Improve how the URL values are transformed in postman export. (#3025)

* Improve how the URL values are transformed.

* Made few changes and also added jsdoc comments

* Removed the querystring values that are getting appended in the host array by filtering you the the queryvalues as we already have the queryparams values inside the request.params object.

* Moved the transformUrl logic to a different file for testing. Added new tests.

* Added tests and updated sanitizeUrl function.

* Updates made in jsdocs.

* Updated function params.

* Review: Code restructure.

* Small changes made.

* Updated the return value when there is an error.

* Changes
This commit is contained in:
Sanjai Kumar 2024-09-18 18:07:55 +05:30 committed by GitHub
parent 07baa63e9d
commit 00fcd30348
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 184 additions and 36 deletions

View File

@ -1,6 +1,105 @@
import map from 'lodash/map';
import * as FileSaver from 'file-saver';
import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems } from 'utils/collections/export';
import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems } from '../collections/export';
/**
* Transforms a given URL string into an object representing the protocol, host, path, query, and variables.
*
* @param {string} url - The raw URL to be transformed.
* @param {Object} params - The params object.
* @returns {Object|null} An object containing the URL's protocol, host, path, query, and variables, or {} if an error occurs.
*/
export const transformUrl = (url, params) => {
if (typeof url !== 'string' || !url.trim()) {
throw new Error("Invalid URL input");
}
const urlRegexPatterns = {
protocolAndRestSeparator: /:\/\//,
hostAndPathSeparator: /\/(.+)/,
domainSegmentSeparator: /\./,
pathSegmentSeparator: /\//,
queryStringSeparator: /\?/
};
const postmanUrl = { raw: url };
/**
* Splits a URL into its protocol, host and path.
*
* @param {string} url - The URL to be split.
* @returns {Object} An object containing the protocol and the raw host/path string.
*/
const splitUrl = (url) => {
const urlParts = url.split(urlRegexPatterns.protocolAndRestSeparator);
if (urlParts.length === 1) {
return { protocol: '', rawHostAndPath: urlParts[0] };
} else if (urlParts.length === 2) {
const [hostAndPath, _] = urlParts[1].split(urlRegexPatterns.queryStringSeparator);
return { protocol: urlParts[0], rawHostAndPath: hostAndPath };
} else {
throw new Error(`Invalid URL format: ${url}`);
}
};
/**
* Splits the host and path from a raw host/path string.
*
* @param {string} rawHostAndPath - The raw host and path string to be split.
* @returns {Object} An object containing the host and path.
*/
const splitHostAndPath = (rawHostAndPath) => {
const [host, path = ''] = rawHostAndPath.split(urlRegexPatterns.hostAndPathSeparator);
return { host, path };
};
try {
const { protocol, rawHostAndPath } = splitUrl(url);
postmanUrl.protocol = protocol;
const { host, path } = splitHostAndPath(rawHostAndPath);
postmanUrl.host = host ? host.split(urlRegexPatterns.domainSegmentSeparator) : [];
postmanUrl.path = path ? path.split(urlRegexPatterns.pathSegmentSeparator) : [];
} catch (error) {
console.error(error.message);
return {};
}
// Construct query params.
postmanUrl.query = params
.filter((param) => param.type === 'query')
.map(({ name, value, description }) => ({ key: name, value, description }));
// Construct path params.
postmanUrl.variable = params
.filter((param) => param.type === 'path')
.map(({ name, value, description }) => ({ key: name, value, description }));
return postmanUrl;
};
/**
* Collapses multiple consecutive slashes (`//`) into a single slash, while skipping the protocol (e.g., `http://` or `https://`).
*
* @param {String} url - A URL string
* @returns {String} The sanitized URL
*
*/
const collapseDuplicateSlashes = (url) => {
return url.replace(/(?<!:)\/{2,}/g, '/');
};
/**
* Replaces all `\\` (backslashes) with `//` (forward slashes) and collapses multiple slashes into one.
*
* @param {string} url - The URL to sanitize.
* @returns {string} The sanitized URL.
*
*/
export const sanitizeUrl = (url) => {
let sanitizedUrl = collapseDuplicateSlashes(url.replace(/\\/g, '//'));
return sanitizedUrl;
};
export const exportCollection = (collection) => {
delete collection.uid;
@ -177,49 +276,17 @@ export const exportCollection = (collection) => {
}
};
const generateHost = (url) => {
try {
const { hostname } = new URL(url);
return hostname.split('.');
} catch (error) {
console.error(`Invalid URL: ${url}`, error);
return [];
}
};
const generatePathParams = (params) => {
return params.filter((param) => param.type === 'path').map((param) => `:${param.name}`);
};
const generateQueryParams = (params) => {
return params
.filter((param) => param.type === 'query')
.map(({ name, value, description }) => ({ key: name, value, description }));
};
const generateVariables = (params) => {
return params
.filter((param) => param.type === 'path')
.map(({ name, value, description }) => ({ key: name, value, description }));
};
const generateRequestSection = (itemRequest) => {
const requestObject = {
method: itemRequest.method,
header: generateHeaders(itemRequest.headers),
auth: generateAuth(itemRequest.auth),
description: itemRequest.docs,
url: {
raw: itemRequest.url,
host: generateHost(itemRequest.url),
path: generatePathParams(itemRequest.params),
query: generateQueryParams(itemRequest.params),
variable: generateVariables(itemRequest.params)
},
auth: generateAuth(itemRequest.auth)
// We sanitize the URL to make sure it's in the right format before passing it to the transformUrl func. This means changing backslashes to forward slashes and reducing multiple slashes to a single one, except in the protocol part.
url: transformUrl(sanitizeUrl(itemRequest.url), itemRequest.params)
};
if (itemRequest.body.mode != 'none') {
if (itemRequest.body.mode !== 'none') {
requestObject.body = generateBody(itemRequest.body);
}
return requestObject;

View File

@ -0,0 +1,81 @@
const { sanitizeUrl, transformUrl } = require('./postman-collection');
describe('transformUrl', () => {
it('should handle basic URL with path variables', () => {
const url = 'https://example.com/{{username}}/api/resource/:id';
const params = [
{ name: 'id', value: '123', type: 'path' },
];
const result = transformUrl(url, params);
expect(result).toEqual({
raw: 'https://example.com/{{username}}/api/resource/:id',
protocol: 'https',
host: ['example', 'com'],
path: ['{{username}}', 'api', 'resource', ':id'],
query: [],
variable: [
{ key: 'id', value: '123' },
]
});
});
it('should handle URL with query parameters', () => {
const url = 'https://example.com/api/resource?limit=10&offset=20';
const params = [
{ name: 'limit', value: '10', type: 'query' },
{ name: 'offset', value: '20', type: 'query' }
];
const result = transformUrl(url, params);
expect(result).toEqual({
raw: 'https://example.com/api/resource?limit=10&offset=20',
protocol: 'https',
host: ['example', 'com'],
path: ['api', 'resource'],
query: [
{ key: 'limit', value: '10' },
{ key: 'offset', value: '20' }
],
variable: []
});
});
it('should handle URL without protocol', () => {
const url = 'example.com/api/resource';
const params = [];
const result = transformUrl(url, params);
expect(result).toEqual({
raw: 'example.com/api/resource',
protocol: '',
host: ['example', 'com'],
path: ['api', 'resource'],
query: [],
variable: []
});
});
});
describe('sanitizeUrl', () => {
it('should replace backslashes with slashes', () => {
const input = 'http:\\\\example.com\\path\\to\\file';
const expected = 'http://example.com/path/to/file';
expect(sanitizeUrl(input)).toBe(expected);
});
it('should collapse multiple slashes into a single slash', () => {
const input = 'http://example.com//path///to////file';
const expected = 'http://example.com/path/to/file';
expect(sanitizeUrl(input)).toBe(expected);
});
it('should handle URLs with mixed slashes', () => {
const input = 'http:\\example.com//path\\to//file';
const expected = 'http://example.com/path/to/file';
expect(sanitizeUrl(input)).toBe(expected);
});
})