feat: Created a mini nodeJS server with NewMan for testing without PostMan GUI.

This will mimic a run in a CD/CI environment or docker container.
This commit is contained in:
Simon Priet
2021-09-08 14:01:19 +02:00
parent 5fbd7c88fa
commit e69a613a37
5610 changed files with 740417 additions and 3 deletions

View File

@@ -0,0 +1,61 @@
var _ = require('lodash').noConflict(),
regexes = {
fold: /\r\n([ \t])/g,
trim: /^\s*(.*\S)?\s*$/, // eslint-disable-line security/detect-unsafe-regex
header: /^((\/\/\s*)?\S+):(.*)$/gm // eslint-disable-line security/detect-unsafe-regex
},
headersCommentPrefix = '//';
module.exports = {
authMap: {
apikeyAuth: 'apikey',
basicAuth: 'basic',
bearerAuth: 'bearer',
digestAuth: 'digest',
hawkAuth: 'hawk',
oAuth1: 'oauth1',
oAuth2: 'oauth2',
ntlmAuth: 'ntlm',
awsSigV4: 'awsv4',
normal: null
},
/**
* Parses a string of headers to an object.
*
* @param {String} data - A string of newline concatenated header key-value pairs.
* @param {?Boolean} [legacy] - A flag to indicate whether the parsing is being done for v1 normalization or v1 to
* v2 conversion.
* @returns {Object[]|*} - The parsed list of header key-value pair objects.
* @private
*/
parseHeaders: function (data, legacy) {
if (!data) { return; }
var head,
headers = [],
statusValue = !legacy,
match = regexes.header.exec(data),
property = legacy ? 'enabled' : 'disabled';
data = data.toString().replace(regexes.fold, '$1');
while (match) {
head = {
key: match[1],
value: match[3].replace(regexes.trim, '$1')
};
if (_.startsWith(head.key, headersCommentPrefix)) {
head[property] = statusValue;
head.key = head.key.replace(headersCommentPrefix, '').trim();
}
headers.push(head);
match = regexes.header.exec(data);
}
return headers;
}
};

View File

@@ -0,0 +1,57 @@
var _ = require('lodash').noConflict(),
util = require('../util'),
/**
* Replenishes missing ids in v2.0.0.x collections.
*
* @param {*} currentItem - A collection entity on which to check for ids.
* @returns {Object|*} - The updated item, with the correct id fields in place.
*/
populateIds = function (currentItem) {
if (!currentItem) { return; }
// ID sanitization
if (currentItem._postman_id) {
currentItem.id = currentItem._postman_id;
delete currentItem._postman_id;
}
!currentItem.id && (currentItem.id = util.uid());
if (currentItem.response && currentItem.response.length) {
_.forEach(currentItem.response, populateIds);
}
if (currentItem.responses && currentItem.responses.length) {
_.forEach(currentItem.responses, populateIds);
}
var itemArray = currentItem.items || currentItem.item;
itemArray && itemArray.length && _.forEach(itemArray, populateIds);
return currentItem;
};
module.exports = {
authMap: {
apikey: 'apikeyAuth',
basic: 'basicAuth',
bearer: 'bearerAuth',
digest: 'digestAuth',
hawk: 'hawkAuth',
oauth1: 'oAuth1',
oauth2: 'oAuth2',
ntlm: 'ntlmAuth',
awsv4: 'awsSigV4',
noauth: null
},
modeMap: {
file: 'binary',
formdata: 'params',
graphql: 'graphql',
raw: 'raw',
urlencoded: 'urlencoded'
},
populateIds: populateIds
};

View File

@@ -0,0 +1,5 @@
exports.SCHEMA_V2_URL = 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json';
exports.SCHEMA_V2_1_0_URL = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json';
exports.SCHEMA_V1_URL = 'https://schema.getpostman.com/json/collection/v1.0.0/collection.json';
exports.PREREQUEST_EXT = '_preRequestScript';
exports.TESTS_EXT = '_tests';

View File

@@ -0,0 +1,152 @@
var semver = require('semver'),
FN = 'function',
generateConverter; // constructor
/**
* Prototype interface definition of a converter
*
* @param {Object} model - A manifest that defines the conversion process.
* @param {String} model.input - The input version to convert from.
* @param {String} model.output - The output version to convert to.
* @param {Function} model.convert - A function to convert entire collections.
* @param {Function} model.create - A function to perform creation operations.
* @param {Function} [model.init] - An initializer function to bootstrap the generated converter.
*
* @throws {Error} If model definition does not meet requirements
*/
generateConverter = function (model) {
var error;
// validate the model
if (!model) {
error = 'invalid definition of converter';
}
// ensure that the input version support is a valid semver
else if (!semver.valid(model.input)) {
error = 'input version support for converter is invalid';
}
// ensure that the output version support is a valid semver
else if (!semver.valid(model.output)) {
error = 'output version support for converter is invalid';
}
else if (typeof model.convert !== FN) {
error = 'convert function is not defined';
}
else if (typeof model.convertSingle !== FN) {
error = 'convertSingle function is not defined';
}
if (semver.satisfies(model.input, model.output)) {
error = 'input version ' + model.input + ' matches output version ' + model.output + ' for converter';
}
// If we had encountered any error during validation, we simply exit by executing the callback and forwarding the
// error.
if (error) {
throw new Error(error);
}
return model;
};
module.exports = {
/**
* All converters
*
* @type {object<Converter>}
*
* @note this form of loading is most likely not browser friendly, find browser alternative
*/
converters: {
'converter-v1-to-v2': require('./v1.0.0/converter-v1-to-v2'),
'converter-v1-to-v21': require('./v1.0.0/converter-v1-to-v21'),
'converter-v2-to-v1': require('./v2.0.0/converter-v2-to-v1'),
'converter-v21-to-v1': require('./v2.1.0/converter-v21-to-v1')
},
/**
* Fetches a converter for the given input and output versions
*
* @param {String} inputVersion - The version to convert from.
* @param {String} outputVersion - The version to convert to.
* @returns {Converter} - A converter for the given set of options.
*/
getConverter: function (inputVersion, outputVersion) {
var converter;
inputVersion = semver.clean(inputVersion);
outputVersion = semver.clean(outputVersion);
for (converter in this.converters) {
// eslint-disable-next-line no-prototype-builtins
converter = this.converters.hasOwnProperty(converter) && this.converters[converter];
if (converter && semver.eq(converter.input, inputVersion) && semver.eq(converter.output, outputVersion)) {
return generateConverter(converter);
}
}
},
/**
* Picks the appropriate converter and converts the given collection.
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after the completion of conversion process.
*/
convert: function (collection, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convert(collection, options, callback);
},
/**
* Picks the appropriate converter and converts the given object.
*
* @param {Object} object - A single V1 request or a V2 Item.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after the completion of conversion process.
*/
convertSingle: function (object, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convertSingle(object, options, callback);
},
/**
* Picks the appropriate converter and converts the given object.
*
* @param {Object} object - A single V1 Response or a V2 Response.
* @param {Object} options - The set of options for response conversion.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
* @returns {*}
*/
convertResponse: function (object, options, callback) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
if (!chosenConverter) {
return callback(new Error('no conversion path found'));
}
return chosenConverter.convertResponse(object, options, callback);
},
/**
* Returns a builder, which can be used to convert individual requests, etc.
*
* @param {Object} options - The set of options for builder creation.
* @returns {Function} - The builder for the given set of options.
*/
builder: function (options) {
var chosenConverter = this.getConverter(options.inputVersion, options.outputVersion);
return chosenConverter.create(options);
}
};

View File

@@ -0,0 +1,885 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
constants = require('../../constants'),
v1Common = require('../../common/v1'),
util = require('../../util'),
headersCommentPrefix = '//',
IS_SUBFOLDER = Symbol('_postman_isSubFolder'),
/**
* A constructor that is capable of being used for one-off conversions of requests, and folders.
*
* @param {Object} options - The set of options for builder construction.
* @class Builders
* @constructor
*/
Builders = function (options) {
this.options = options || {};
},
/**
* Parse formdata & urlencoded request data.
* - filter params missing property `key`
* - handle file type param value
* -cleanup `enabled` & `description`
*
* @param {Object[]} data - Data to parse.
* @param {?Boolean} [retainEmpty] - To retain empty values or not.
* @returns {Object[]} - Parsed data.
*/
parseFormData = function (data, retainEmpty) {
if (!(Array.isArray(data) && data.length)) { return []; }
var formdata = [],
i,
ii,
param;
for (i = 0, ii = data.length; i < ii; i++) {
// clone param to avoid mutating data.
// eslint-disable-next-line prefer-object-spread
param = Object.assign({}, data[i]);
// skip if param is missing property `key`,
// `key` is a required property, value can be null/undefined.
// because in a FormParam property lists, key is used for indexing.
if (_.isNil(param.key)) {
continue;
}
// for file `type`, set `value` to `src`
if (param.type === 'file' && !param.src && param.value) {
param.src = (_.isString(param.value) || _.isArray(param.value)) ? param.value : null;
delete param.value;
}
// `hasOwnProperty` check ensures that it don't delete undefined property: `enabled`
if (Object.prototype.hasOwnProperty.call(param, 'enabled')) {
// set `disabled` flag if `enabled` is false.
param.enabled === false && (param.disabled = true);
delete param.enabled; // cleanup
}
// Prevent empty descriptions from showing up in the converted results. This keeps collections clean.
util.cleanEmptyValue(param, 'description', retainEmpty);
formdata.push(param);
}
return formdata;
};
_.assign(Builders.prototype, {
/**
* Constructs a V2 compatible "info" object from a V1 Postman Collection
*
* @param {Object} collectionV1 - A v1 collection to derive collection metadata from.
* @returns {Object} - The resultant v2 info object.
*/
info: function (collectionV1) {
var info = {
_postman_id: collectionV1.id || util.uid(),
name: collectionV1.name
};
if (collectionV1.description) { info.description = collectionV1.description; }
else if (this.options.retainEmptyValues) { info.description = null; }
info.schema = constants.SCHEMA_V2_URL;
return info;
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variable: function (entity, options) {
return util.handleVars(entity, options);
},
/**
* Constructs a V2 compatible URL object from a V1 request
*
* @param {Object} requestV1 - The source v1 request to extract the URL from.
* @returns {String|Object} - The resultant URL.
*/
url: function (requestV1) {
var queryParams = [],
pathVariables = [],
traversedVars = {},
queryParamAltered,
retainIds = this.options.retainIds,
parsed = util.urlparse(requestV1.url),
retainEmpty = this.options.retainEmptyValues;
// add query params
_.forEach(requestV1.queryParams, function (queryParam) {
(queryParam.enabled === false) && (queryParam.disabled = true);
delete queryParam.enabled;
util.cleanEmptyValue(queryParam, 'description', retainEmpty);
if (_.has(queryParam, 'equals')) {
if (queryParam.equals) {
(queryParam.value === null) && (queryParam.value = '');
}
else {
// = is not appended when the value is null. However,
// non empty value should be preserved
queryParam.value = queryParam.value || null;
}
queryParamAltered = true;
delete queryParam.equals;
}
queryParams.push(queryParam);
});
// only add query params from URL if not given explicitly
if (!_.size(queryParams)) {
// parsed query params are taken from the url, so no descriptions are available from them
queryParams = parsed.query;
}
// Merge path variables
_.forEach(requestV1.pathVariableData, function (pathVariable) {
pathVariable = _.clone(pathVariable);
util.cleanEmptyValue(pathVariable, 'description', retainEmpty);
if (!retainIds && pathVariable.id) {
delete pathVariable.id;
}
pathVariables.push(pathVariable);
traversedVars[pathVariable.key] = true;
});
// pathVariables in v1 are of the form {foo: bar}, so no descriptions can be obtained from them
_.forEach(requestV1.pathVariables, function (value, key) {
!traversedVars[key] && pathVariables.push({
value: value,
key: key
});
});
!_.isEmpty(queryParams) && (parsed.query = queryParams);
!_.isEmpty(pathVariables) && (parsed.variable = pathVariables);
// If the query params have been altered, update the raw stringified URL
queryParamAltered && (parsed.raw = util.urlunparse(parsed));
// return the objectified URL only if query param or path variable descriptions are present, string otherwise
return (parsed.query || parsed.variable) ? parsed : (parsed.raw || requestV1.url);
},
/**
* Extracts the HTTP Method from a V1 request
*
* @param {Object} requestV1 - The v1 request to extract the request method from.
* @returns {String} - The extracted request method.
*/
method: function (requestV1) {
return requestV1.method;
},
/**
* Constructs an array of Key-Values from a raw HTTP Header string.
*
* @param {Object} requestV1 - The v1 request to extract header information from.
* @returns {Object[]} - A list of header definition objects.
*/
header: function (requestV1) {
if (_.isArray(requestV1.headers)) {
return requestV1.headers;
}
var headers = [],
traversed = {},
headerData = requestV1.headerData || [],
retainEmpty = this.options.retainEmptyValues;
_.forEach(headerData, function (header) {
if (_.startsWith(header.key, headersCommentPrefix) || (header.enabled === false)) {
header.disabled = true;
header.key = header.key.replace(headersCommentPrefix, '').trim();
}
// prevent empty header descriptions from showing up in converted results. This keeps the collections clean
util.cleanEmptyValue(header, 'description', retainEmpty);
delete header.enabled;
headers.push(header); // @todo Improve this sequence to account for multi-valued headers
traversed[header.key] = true;
});
// requestV1.headers is a string, so no descriptions can be obtained from it
_.forEach(v1Common.parseHeaders(requestV1.headers), function (header) {
!traversed[header.key] && headers.push(header);
});
return headers;
},
/**
* Constructs a V2 Request compatible "body" object from a V1 Postman request
*
* @param {Object} requestV1 - The v1 request to extract the body from.
* @returns {{mode: *, content: (*|string)}}
*/
body: function (requestV1) {
var modes = {
binary: 'file',
graphql: 'graphql',
params: 'formdata',
raw: 'raw',
urlencoded: 'urlencoded'
},
data = {},
rawModeData,
graphqlModeData,
dataMode = modes[requestV1.dataMode],
retainEmpty = this.options.retainEmptyValues,
bodyOptions = {},
mode,
// flag indicating that all the props which holds request body data
// i.e, data (urlencoded, formdata), rawModeData (raw, file) and graphqlModeData (graphql)
// are empty.
emptyBody = _.isEmpty(requestV1.data) && _.isEmpty(requestV1.rawModeData) &&
_.isEmpty(requestV1.graphqlModeData);
// set body to null if:
// 1. emptyBody is true and dataMode is unset
// 2. dataMode is explicitly set to null
// @note the explicit null check is added to ensure that body is not set
// in case the dataMode was set to null by the app.
if ((!dataMode && emptyBody) || requestV1.dataMode === null) {
return retainEmpty ? null : undefined;
}
// set `rawModeData` if its a string
if (_.isString(requestV1.rawModeData)) {
rawModeData = requestV1.rawModeData;
}
// check if `rawModeData` is an array like: ['rawModeData']
else if (Array.isArray(requestV1.rawModeData) &&
requestV1.rawModeData.length === 1 &&
_.isString(requestV1.rawModeData[0])) {
rawModeData = requestV1.rawModeData[0];
}
// set graphqlModeData if its not empty
if (!_.isEmpty(requestV1.graphqlModeData)) {
graphqlModeData = requestV1.graphqlModeData;
}
// set data.mode.
// if dataMode is not set, infer from data or rawModeData or graphqlModeData
if (dataMode) {
data.mode = dataMode;
}
// at this point we are sure that the body is not empty so let's
// infer the data mode.
// @note its possible that multiple body types are set e.g, both
// rawModeData and graphqlModeData are set. So, the priority will be:
// raw -> formdata -> graphql (aligned with pre-graphql behaviour).
//
// set `formdata` if rawModeData is not set and data is an array
// `data` takes higher precedence over `rawModeData`.
else if (!rawModeData && Array.isArray(requestV1.data || requestV1.rawModeData)) {
data.mode = 'formdata';
}
// set `graphql` if graphqlModeData is set
else if (!rawModeData && graphqlModeData) {
data.mode = 'graphql';
}
// set `raw` mode as default
else {
data.mode = 'raw';
}
if (data.mode === 'raw') {
if (rawModeData) {
data[data.mode] = rawModeData;
}
else if (_.isString(requestV1.data)) {
data[data.mode] = requestV1.data;
}
else {
// empty string instead of retainEmpty check to have parity with other modes.
data[data.mode] = '';
}
}
else if (data.mode === 'graphql') {
data[data.mode] = graphqlModeData;
}
else if (data.mode === 'file') {
// rawModeData can be string or undefined.
data[data.mode] = { src: rawModeData };
}
else {
// parse data for formdata or urlencoded data modes.
// `rawModeData` is checked in case its of type `data`.
data[data.mode] = parseFormData(requestV1.data || requestV1.rawModeData, retainEmpty);
}
if (requestV1.dataOptions) {
// Convert v1 mode to v2 mode
for (mode in modes) {
if (!_.isEmpty(requestV1.dataOptions[mode])) {
bodyOptions[modes[mode]] = requestV1.dataOptions[mode];
}
}
!_.isEmpty(bodyOptions) && (data.options = bodyOptions);
}
if (requestV1.dataDisabled) { data.disabled = true; }
else if (retainEmpty) { data.disabled = false; }
return data;
},
/**
* Constructs a V2 "events" object from a V1 Postman Request
*
* @param {Object} entityV1 - The v1 entity to extract script information from.
* @returns {Object[]|*}
*/
event: function (entityV1) {
if (!entityV1) { return; }
const retainIds = this.options.retainIds;
// if prioritizeV2 is true, events is used as the source of truth
if ((util.notLegacy(entityV1, 'event') || this.options.prioritizeV2) && !_.isEmpty(entityV1.events)) {
// in v1, `events` is regarded as the source of truth if it exists, so handle that first and bail out.
// @todo: Improve this to order prerequest events before test events
_.forEach(entityV1.events, function (event) {
!event.listen && (event.listen = 'test');
// just delete the event.id if retainIds is not set
if (!retainIds && event.id) {
delete event.id;
}
if (event.script) {
// just delete the script.id if retainIds is not set
if (!retainIds && event.script.id) {
delete event.script.id;
}
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return entityV1.events;
}
var events = [];
// @todo: Extract both flows below into a common method
if (entityV1.tests) {
events.push({
listen: 'test',
script: {
type: 'text/javascript',
exec: _.isString(entityV1.tests) ?
entityV1.tests.split('\n') :
entityV1.tests
}
});
}
if (entityV1.preRequestScript) {
events.push({
listen: 'prerequest',
script: {
type: 'text/javascript',
exec: _.isString(entityV1.preRequestScript) ?
entityV1.preRequestScript.split('\n') :
entityV1.preRequestScript
}
});
}
return events.length ? events : undefined;
},
/**
* A number of auth parameter names have changed from V1 to V2. This function calls the appropriate
* mapper function, and creates the V2 auth parameter object.
*
* @param {Object} entityV1 - The v1 entity to derive auth information from.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
* @returns {{type: *}}
*/
auth: function (entityV1, options) {
if (!entityV1) { return; }
if ((util.notLegacy(entityV1, 'auth') || this.options.prioritizeV2) && entityV1.auth) {
return util.authArrayToMap(entityV1, options);
}
if (!entityV1.currentHelper || (entityV1.currentHelper === null) || (entityV1.currentHelper === 'normal')) {
return;
}
var params,
type = v1Common.authMap[entityV1.currentHelper] || entityV1.currentHelper,
auth = {
type: type
};
// Some legacy versions of the App export Helper Attributes as a string.
if (_.isString(entityV1.helperAttributes)) {
try {
entityV1.helperAttributes = JSON.parse(entityV1.helperAttributes);
}
catch (e) {
return;
}
}
if (entityV1.helperAttributes && util.authMappersFromLegacy[entityV1.currentHelper]) {
params = util.authMappersFromLegacy[entityV1.currentHelper](entityV1.helperAttributes);
}
params && (auth[type] = params);
return auth;
},
/**
* Creates a V2 format request from a V1 Postman Collection Request
*
* @param {Object} requestV1 - The v1 request to be transformed.
* @returns {Object} - The converted v2 request.
*/
request: function (requestV1) {
var self = this,
request = {},
retainEmpty = self.options.retainEmptyValues,
units = ['auth', 'method', 'header', 'body', 'url'];
units.forEach(function (unit) {
request[unit] = self[unit](requestV1);
});
if (requestV1.description) { request.description = requestV1.description; }
else if (retainEmpty) { request.description = null; }
return request;
},
/**
* Converts a V1 cookie to a V2 cookie.
*
* @param {Object} cookieV1 - The v1 cookie object to convert.
* @returns {{expires: string, hostOnly: *, httpOnly: *, domain: *, path: *, secure: *, session: *, value: *}}
*/
cookie: function (cookieV1) {
return {
expires: (new Date(cookieV1.expirationDate * 1000)).toString(),
hostOnly: cookieV1.hostOnly,
httpOnly: cookieV1.httpOnly,
domain: cookieV1.domain,
path: cookieV1.path,
secure: cookieV1.secure,
session: cookieV1.session,
value: cookieV1.value,
key: cookieV1.name
};
},
/**
* Gets the saved request for the given response, and handles edge cases between Apps & Sync
*
* Handles a lot of edge cases, so the code is not very clean.
*
* The Flow followed here is:
*
* If responseV1.requestObject is present
* If it is a string
* Try parsing it as JSON
* If parsed,
* return it
* else
* It is a request ID
* If responseV1.request is present
* If it is a string
* Try parsing it as JSON
* If parsed,
* return it
* else
* It is a request ID
* Look up the collection for the request ID and return it, or return undefined.
*
* @param {Object} responseV1 - The v1 response to be converted.
* @returns {Object} - The converted saved request, in v2 format.
*/
savedRequest: function (responseV1) {
var self = this,
associatedRequestId;
if (responseV1.requestObject) {
if (_.isString(responseV1.requestObject)) {
try {
return JSON.parse(responseV1.requestObject);
}
catch (e) {
// if there was an error parsing it as JSON, it's probably an ID, so store it in the ID variable
associatedRequestId = responseV1.requestObject;
}
}
else {
return responseV1.requestObject;
}
}
if (responseV1.request) {
if (_.isString(responseV1.request)) {
try {
return JSON.parse(responseV1.request);
}
catch (e) {
// if there was an error parsing it as JSON, it's probably an ID, so store it in the ID variable
associatedRequestId = responseV1.request;
}
}
else {
return responseV1.request;
}
}
// we have a request ID
return associatedRequestId && _.get(self, ['cache', associatedRequestId]);
},
/**
* Since a V2 response contains the entire associated request that was sent, creating the response means it
* also must use the V1 request.
*
* @param {Object} responseV1 - The response object to convert from v1 to v2.
* @returns {Object} - The v2 response object.
*/
singleResponse: function (responseV1) {
var response = {},
self = this,
originalRequest;
originalRequest = self.savedRequest(responseV1);
// add ids to the v2 result only if both: the id and retainIds are truthy.
// this prevents successive exports to v2 from being overwhelmed by id diffs
self.options.retainIds && (response.id = responseV1.id || util.uid());
response.name = responseV1.name || 'response';
response.originalRequest = originalRequest ? self.request(originalRequest) : undefined;
response.status = responseV1.responseCode && responseV1.responseCode.name || undefined;
response.code = responseV1.responseCode && Number(responseV1.responseCode.code) || undefined;
response._postman_previewlanguage = responseV1.language;
response._postman_previewtype = responseV1.previewType;
response.header = responseV1.headers;
response.cookie = _.map(responseV1.cookies, function (cookie) {
return self.cookie(cookie);
});
response.responseTime = responseV1.time;
response.body = responseV1.text;
return response;
},
/**
* Constructs an array of "sample" responses (compatible with a V2 collection)
* from a Postman Collection V1 Request.
*
* If response ordering via `responses_order` field is present,
* ensure the ordering is respected while constructing responses array.
*
* @param {Object} requestV1 - The v1 request object to extract response information from.
* @returns {Object[]} - The list of v2 response definitions.
*/
response: function (requestV1) {
var self = this,
responses = _.get(requestV1, 'responses', []),
responsesCache = _.keyBy(responses, 'id'),
responses_order = _.get(requestV1, 'responses_order', []);
// If ordering of responses is not available
// Create a default ordering using the `responses` field
if (!(responses_order && responses_order.length)) {
responses_order = _.map(responses, 'id');
}
// Filter out any response id that is not available
responses_order = _.filter(responses_order, function (responseId) {
return _.has(responsesCache, responseId);
});
return _.map(responses_order, function (responseId) {
return self.singleResponse(responsesCache[responseId]);
});
},
/**
* Creates a V2 compatible ``item`` from a V1 Postman Collection Request
*
* @param {Object} requestV1 - Postman collection V1 request.
* @returns {Object} - The converted request object, in v2 format.
*/
singleItem: function (requestV1) {
if (!requestV1) { return; }
var self = this,
retainIds = self.options.retainIds,
units = ['request', 'response'],
variable = self.variable(requestV1, { retainIds: retainIds }),
item = {
name: requestV1.name || '', // Inline building to avoid additional function call
event: self.event(requestV1)
};
retainIds && (item.id = requestV1.id || util.uid());
// add protocolProfileBehavior property from requestV1 to the item
util.addProtocolProfileBehavior(requestV1, item);
units.forEach(function (unit) {
item[unit] = self[unit](requestV1);
});
variable && variable.length && (item.variable = variable);
return item;
},
/**
* Constructs an array of Items & ItemGroups compatible with the V2 format.
*
* @param {Object} collectionV1 - The v1 collection to derive folder information from.
* @returns {Object[]} - The list of item group definitions.
*/
itemGroups: function (collectionV1) {
var self = this,
items = [],
itemGroupCache = {},
retainEmpty = self.options.retainEmptyValues;
// Read all folder data, and prep it so that we can throw subfolders in the right places
itemGroupCache = _.reduce(collectionV1.folders, function (accumulator, folder) {
if (!folder) { return accumulator; }
var retainIds = self.options.retainIds,
auth = self.auth(folder),
event = self.event(folder),
variable = self.variable(folder, { retainIds: retainIds }),
result = {
name: folder.name,
item: []
};
retainIds && (result.id = folder.id || util.uid());
if (folder.description) { result.description = folder.description; }
else if (retainEmpty) { result.description = null; }
(auth || (auth === null)) && (result.auth = auth);
event && (result.event = event);
variable && variable.length && (result.variable = variable);
util.addProtocolProfileBehavior(folder, result);
accumulator[folder.id] = result;
return accumulator;
}, {});
// Populate each ItemGroup with subfolders
_.forEach(collectionV1.folders, function (folderV1) {
if (!folderV1) { return; }
var itemGroup = itemGroupCache[folderV1.id],
hasSubfolders = folderV1.folders_order && folderV1.folders_order.length,
hasRequests = folderV1.order && folderV1.order.length;
// Add subfolders
hasSubfolders && _.forEach(folderV1.folders_order, function (subFolderId) {
if (!itemGroupCache[subFolderId]) {
// todo: figure out what to do when a collection contains a subfolder ID,
// but the subfolder is not actually there.
return;
}
itemGroupCache[subFolderId][IS_SUBFOLDER] = true;
itemGroup.item.push(itemGroupCache[subFolderId]);
});
// Add items
hasRequests && _.forEach(folderV1.order, function (requestId) {
if (!self.cache[requestId]) {
// todo: what do we do here??
return;
}
itemGroup.item.push(self.singleItem(self.cache[requestId]));
});
});
// This compromises some self-healing, which was originally present, but the performance cost of
// doing self-healing the right way is high, so we directly rely on collectionV1.folders_order
// The self-healing way would be to iterate over itemGroupCache directly, but preserving the right order
// becomes a pain in that case.
_.forEach(_.uniq(collectionV1.folders_order || _.map(collectionV1.folders, 'id')), function (folderId) {
var itemGroup = itemGroupCache[folderId];
itemGroup && !_.get(itemGroup, IS_SUBFOLDER) && items.push(itemGroup);
});
// This is useful later
self.itemGroupCache = itemGroupCache;
return _.compact(items);
},
/**
* Creates a V2 compatible array of items from a V1 Postman Collection
*
* @param {Object} collectionV1 - A Postman Collection object in the V1 format.
* @returns {Object[]} - The list of item groups (folders) in v2 format.
*/
item: function (collectionV1) {
var self = this,
requestsCache = _.keyBy(collectionV1.requests, 'id'),
allRequests = _.map(collectionV1.requests, 'id'),
result;
self.cache = requestsCache;
result = self.itemGroups(collectionV1);
_.forEach(_.intersection(collectionV1.order, allRequests), function (requestId) {
var request = self.singleItem(requestsCache[requestId]);
request && (result.push(request));
});
return result;
}
});
module.exports = {
input: '1.0.0',
output: '2.0.0',
Builders: Builders,
/**
* Converts a single V1 request to a V2 item.
*
* @param {Object} request - The v1 request to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convertSingle: function (request, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.singleItem(_.cloneDeep(request));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a single V1 Response to a V2 Response.
*
* @param {Object} response - The v1 response to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.singleResponse(_.cloneDeep(response));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The v1 collection to convert to v2.
* @param {Object} options - The set of options for v1 -> v2 conversion.
* @param {Function} callback - The function to be invoked when the conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
event,
variable,
newCollection = {},
units = ['info', 'item'],
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds };
try {
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(event = builders.event(collection)) && (newCollection.event = event);
(variable = builders.variable(collection, varOpts)) && (newCollection.variable = variable);
util.addProtocolProfileBehavior(collection, newCollection);
}
catch (e) {
if (callback) {
return callback(e, null);
}
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,157 @@
/* eslint-disable object-shorthand */
var inherits = require('inherits'),
_ = require('lodash').noConflict(),
url = require('../../url'),
util = require('../../util'),
constants = require('../../constants'),
BaseBuilders = require('./converter-v1-to-v2').Builders,
Builders;
inherits(Builders = function () {
Builders.super_.apply(this, arguments);
}, BaseBuilders);
_.assign(Builders.prototype, {
/**
* Derives v2.1.0 collection info from a v1.0.0 collection object.
*
* @param {Object} collectionV1 - The v1.0.0 collection object to be converted to v2.1.0.
* @return {Object} - The compiled v2.x collection info manifest.
*/
info: function (collectionV1) {
var info = Builders.super_.prototype.info.call(this, collectionV1);
info.schema = constants.SCHEMA_V2_1_0_URL;
return info;
},
/**
* Converts collection request urls from v1.0.0 to v2.1.0
*
* @param {Object} requestV1 - The v1.0.0 request url to be converted to v2.1.0.
* @return {Object} - The objectified v2.1.0 compliant URL.
*/
url: function (requestV1) {
var v21Url = Builders.super_.prototype.url.call(this, requestV1);
return _.isString(v21Url) ? url.parse(v21Url) : v21Url;
},
/**
* A number of auth parameter names have changed from V1 to V2. This function calls the appropriate
* mapper function, and creates the V2 auth parameter object.
*
* @param {Object} entityV1 - A Collection V1 compliant request instance.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
* @returns {{type: *}} - The v2.1.0 compliant request object
*/
auth: function (entityV1, options) {
// if the current auth manifest is at a parent level, no further transformation is needed.
// @todo: Possible dead code, prune when confirmed
if (util.notLegacy(entityV1, 'auth') && entityV1.currentHelper) {
return util.sanitizeAuthArray(entityV1, options);
}
var auth = Builders.super_.prototype.auth.call(this, entityV1, options);
return util.authMapToArray({ auth: auth }, options);
}
});
module.exports = {
input: '1.0.0',
output: '2.1.0',
Builders: Builders,
/**
* Converts a single V1 request to a v2.1.0 item.
*
* @param {Object} request - The v1.0.0 request to be converted to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion sequence.
* @param {?Function} callback - The function invoked to mark the end of the current conversion process.
* @returns {*}
*/
convertSingle: function (request, options, callback) {
var err,
converted,
builders = new Builders(options);
try { converted = builders.singleItem(_.cloneDeep(request)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a single V1 Response to a v2.1.0 Response.
*
* @param {Object} response - The V1 compliant response to convert to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion process.
* @param {?Function} callback - The function invoked to mark the completion of the response conversion.
* @returns {*}
*/
convertResponse: function (response, options, callback) {
var err,
converted,
builders = new Builders(options);
try { converted = builders.singleResponse(_.cloneDeep(response)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The V1 collection instance to convert to a v2.1.0 format.
* @param {Object} options - The set of options for the current conversion sequence.
* @param {?Function} callback - The function invoked to mark the completion of the conversion process/
* @returns {*}
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
event,
variable,
newCollection = {},
units = ['info', 'item'],
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds };
try {
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(event = builders.event(collection)) && (newCollection.event = event);
(variable = builders.variable(collection, varOpts)) && (newCollection.variable = variable);
util.addProtocolProfileBehavior(collection, newCollection);
}
catch (e) {
if (callback) { return callback(e); }
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,797 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
util = require('../../util'),
v2Common = require('../../common/v2'),
Builders = function (options) {
this.options = options || {};
};
_.assign(Builders.prototype, {
/**
* Converts v2 style auth manifests into their v1 equivalents.
*
* @param {Object} entityV2 - The v1 auth manifest to be transformed into v1.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
*
* @returns {Object} The transformed auth object.
*/
auth: function (entityV2, options) {
return util.authMapToArray(entityV2, options);
},
/**
* Constructs a V1 "events" object from a V2 Postman entity
*
* @param {Object} entityV2 - The v2 event object to be converted.
* @returns {Object[]} - The resultant v1 script definition.
*/
events: function (entityV2) {
// events is treated as the source of truth in v1, so handle that first and bail out.
var source = entityV2.events || entityV2.event;
if (_.isArray(source)) {
// @todo: Improve this to order prerequest events before test events
_.forEach(source, function (event) {
!event.listen && (event.listen = 'test');
if (event.script) {
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return source;
}
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variables: function (entity, options) {
return util.handleVars(entity, options);
},
/**
* Extracts all subfolders from a v2.0.0 collection or folder
*
* @param {Object} folderOrCollection - Thw entity to extract child items from.
* @returns {Object[]} - The list of extracted folder items.
*/
extractFolderItems: function (folderOrCollection) {
if (!folderOrCollection) { return; }
var i,
self = this,
folders = [],
items = folderOrCollection.item || folderOrCollection.items;
!_.isArray(items) && (items = [items]);
for (i = 0; i < items.length; i++) {
if (items[i] && (items[i].items || items[i].item)) {
folders.push(items[i]);
folders = [].concat(folders, self.extractFolderItems(items[i]));
}
}
return folders;
},
/**
* Extracts all requests from a v2.0.0 collection or folder
*
* @param {Object} folderOrCollection - The object to extract sub-items from.
* @returns {Object[]} - The list of extracted folder items.
*/
extractItems: function (folderOrCollection) {
if (!folderOrCollection) { return; }
var i,
self = this,
requests = [],
isFolder,
items = folderOrCollection.item || folderOrCollection.items;
!_.isArray(items) && (items = [items]);
for (i = 0; i < items.length; i++) {
isFolder = items[i] && (items[i].items || items[i].item);
if (items[i]) {
isFolder ? (requests = [].concat(requests, self.extractItems(items[i]))) : requests.push(items[i]);
}
}
return requests;
},
/**
* Constructs a monolithic raw HTTP header block from a V2 header array
*
* @param {Object} item - The wrapper object to extract headers from.
* @returns {*|string} - The resultant header string.
*/
headers: function (item) {
if (!(item && item.request)) { return; }
return _.map(item.request.headers || item.request.header, function (header) {
return (header.disabled ? '// ' : '') + header.key + ': ' + header.value;
}).join('\n');
},
/**
* Detects the data mode from a given Postman Collection V2 item
*
* @param {Object} item - .
* @returns {*|number|string}
*/
dataMode: function (item) {
return v2Common.modeMap[_.get(item, 'request.body.mode')];
},
/**
* Returns the appropriate request data based on the data mode.
*
* @param {Object} item - The .
* @returns {Object} - The converted request body.
*/
data: function (item) {
var self = this,
mode = _.get(item, 'request.body.mode'),
retainEmpty = this.options.retainEmptyValues;
if (mode === 'raw' || mode === 'file' || mode === 'graphql' || !mode) {
return retainEmpty ? null : undefined;
}
return _.map(item.request.body[mode], function (elem) {
// Only update the value in v1 if src in v2 is non-empty
if (elem && elem.type === 'file' && _.has(elem, 'src')) {
elem.value = (_.isString(elem.src) || _.isArray(elem.src)) ? elem.src : null;
delete elem.src;
}
// Prevents empty request body descriptions from showing up in the result, keeps collections clean.
elem.description = self.description(elem.description);
_.has(elem, 'disabled') && (elem.enabled = !elem.disabled);
delete elem.disabled;
return elem;
});
},
/**
* In case of raw request bodies, this constructs the proper raw data from a V2 item.
*
* @param {Object} item - The v2 request to derive body information from.
* @returns {String} - The inferred v1 request body mode.
*/
rawModeData: function (item) {
var mode = _.get(item, 'request.body.mode');
if (mode === 'raw') {
return item.request.body.raw;
}
else if (mode === 'file') {
return _.get(item.request.body, 'file.src');
}
return this.options.retainEmptyValues ? null : undefined;
},
/**
* Returns GraphQL data if mode is set to graphql.
*
* @param {Object} item - The v2 request to derive information form.
* @returns {Object} - GraphQL request body.
*/
graphqlModeData: function (item) {
var mode = _.get(item, 'request.body.mode');
if (mode === 'graphql') {
return item.request.body.graphql;
}
return this.options.retainEmptyValues ? null : undefined;
},
/**
* Creates options for body from v2 format.
*
* @param {Object} item - The v2 request to derive information form.
* @returns {Object} - The converted body options.
*/
dataOptions: function (item) {
var options = _.get(item, 'request.body.options'),
bodyOptions = {},
mode;
if (!options) {
return;
}
// Convert v2 mode to v1 mode
for (mode in v2Common.modeMap) {
if (!_.isEmpty(options[mode])) {
bodyOptions[v2Common.modeMap[mode]] = options[mode];
}
}
return _.isEmpty(bodyOptions) ? undefined : bodyOptions;
},
/**
* Creates an object of path-variables and their values from a V2 item
*
* @param {Object} item - The wrapper object containing path variable information.
* @returns {Object} - The resultant hash of path variables.
*/
pathVariables: function (item) {
var variable = _.get(item, 'request.url.variables') || _.get(item, 'request.url.variable');
if (!variable) { return; }
return _.transform(variable, function (accumulator, v) {
accumulator[v.key || v.id] = v.value; // v2.0.0 supports both key and id, v2.1.0 will drop id support
}, {});
},
/**
* Creates a V1 URL from a V2 item
*
* @param {Object} item - The v2 request to extract the URL from.
* @returns {String} - The extracted URL of the request.
*/
url: function (item) {
var url = _.get(item, 'request.url');
if (_.isString(url)) {
return url;
}
if (!url) {
return '';
}
return util.urlunparse(url);
},
/**
* Extracts test from a V2 collection
*
* @param {Object} item - The wrapper object to extract test code from.
* @returns {String} - The resultant test script code.
*/
tests: function (item) {
var allEvents = item.events || item.event,
events;
// Nothing to do if the item has no associated events
if (!allEvents) {
return;
}
events = _.filter(allEvents, { listen: 'test' });
return _.map(events, function (event) {
var tests = _.get(event, 'script.exec');
// @todo: Possible dead code, remove when confirmed
if (_.isArray(tests)) {
tests = tests.join('\n');
}
return tests;
}).join('\n');
},
/**
* Extracts the pre-request script from an Item
*
* @param {Object} item - The wrapper object to extract pre-request code from.
* @returns {String} - The resultant pre-request script code.
*/
preRequestScript: function (item) {
var allEvents = item.events || item.event,
events;
// Nothing to do if the item has no associated events
if (!allEvents) {
return;
}
events = _.filter(allEvents, { listen: 'prerequest' });
return _.map(events, function (event) {
var tests = _.get(event, 'script.exec');
// @todo: Possible dead code, remove when confirmed
if (_.isArray(tests)) {
tests = tests.join('\n');
}
return tests;
}).join('\n');
},
/**
* Converts a V2 cookie to a V1 cookie.
*
* @param {Object} cookieV2 - The v2 cookie object to be converted.
* @returns {{expirationDate: *, hostOnly: *, httpOnly: *,
* domain: (any), path: (any), secure: *, session: *, value: *, name: *}}
*/
cookie: function (cookieV2) {
return {
expirationDate: cookieV2.expires,
hostOnly: cookieV2.hostOnly,
httpOnly: cookieV2.httpOnly,
domain: cookieV2.domain,
path: cookieV2.path,
secure: cookieV2.secure,
session: cookieV2.session,
value: cookieV2.value,
name: cookieV2.key || cookieV2.name
};
},
/**
* Converts a V2 response object to a V1 response
*
* @param {Object} responseV2 - The v2 response to be converted.
* @returns {Object} - The converted v1 response.
*/
response: function (responseV2) {
var self = this,
response = {},
id = responseV2.id || responseV2._postman_id,
originalRequest = responseV2.originalRequest || responseV2.request;
// the true in the next line ensures that we don't recursively go on processing responses in a request.
response.request = originalRequest ? self.request({ request: originalRequest }, undefined, true) : undefined;
// add the requestObject to the response (needed by sync)
try {
response.request && (response.requestObject = JSON.stringify(response.request));
}
catch (e) { /* It's fine, not a fatal error, just move on. */ }
// do not attempt to regenerate response id here when `retainIds` is set to false
// if id is changed here the parent's `responses_order` also needs to be changed
// that can't be done yet
response.id = id || util.uid();
response.name = responseV2.name;
response.status = responseV2.status;
response.responseCode = {
code: responseV2.code,
name: responseV2.status,
// TODO: get a list of descriptions
detail: ''
};
response.language = responseV2._postman_previewlanguage || 'Text';
response.previewType = responseV2._postman_previewtype || 'html';
response.time = responseV2.responseTime;
response.headers = responseV2.headers || responseV2.header;
response.cookies = _.map(responseV2.cookies || responseV2.cookie, self.cookie);
response.text = responseV2.body;
response.rawDataType = 'text';
return response;
},
/**
* Extracts the array of responses from a V2 Item.
*
* @param {Object} item - The v2 item to extract saved responses from.
* @returns {Object[]} - The list of saved response objects for the current request.
*/
responses: function (item) {
var self = this,
allResponses = item.responses || item.response;
if (!allResponses) { return; }
return _.map(allResponses, function (response) {
return self.response(response, item);
});
},
/**
* Creates an ordering field for responses of a V2 Item.
*
* @param {Object} item - The v2 item to extract saved responses from.
* @returns {Object[]} - The order of responses within the V2 Item.
*/
responses_order: function (item) {
var allResponses = item.responses || item.response;
if (!allResponses) {
return [];
}
return _.map(allResponses, 'id');
},
/**
* Converts a V2 request to a V1 request.
*
* @param {Object} item - The v2 item to be converted.
* @param {Object} collectionId - The collection id related to the current conversion routine.
* @param {Boolean} [skipResponses=false] - When set to true, excludes saved responses from the result.
* @returns {{id: *, name: *, description: (*|string|builders.description), url: *, collectionId: *, method: *,
* currentHelper: *, helperAttributes: *}|*}
*/
request: function (item, collectionId, skipResponses) {
if (!item) { return; }
var units = ['headers', 'dataMode', 'data', 'rawModeData', 'graphqlModeData',
'pathVariables', 'tests', 'preRequestScript', 'url', 'dataOptions'],
self = this,
request,
description,
currentHelper,
helperAttributes,
req = item && item.request,
v2Auth = req && req.auth,
auth = self.auth(req),
events = self.events(item),
variables = self.variables(item, { retainIds: self.options.retainIds }),
url = req && req.url,
retainEmpty = self.options.retainEmptyValues,
urlObj = _.isString(url) ? util.urlparse(url) : url;
if (!skipResponses) {
units.push('responses');
units.push('responses_order');
}
if (v2Auth && v2Auth.type) {
// @todo: Add support for custom auth helpers
currentHelper = v2Common.authMap[v2Auth.type];
if (util.authMappersFromCurrent[currentHelper]) {
_.isArray(v2Auth[v2Auth.type]) && (v2Auth = util.authArrayToMap(req));
helperAttributes = util.authMappersFromCurrent[currentHelper](v2Auth[v2Auth.type]);
}
else {
helperAttributes = null;
}
}
else if (v2Auth === null) {
currentHelper = null;
helperAttributes = null;
}
request = {
// do not attempt to regenerate request id here when `retainIds` is set to false
// if id is changed here the parent's `order` also needs to be changed
// that can't be done yet
id: item.id || item._postman_id || util.uid(),
name: item.name,
collectionId: collectionId,
method: item.request ? item.request.method : undefined,
currentHelper: currentHelper,
helperAttributes: helperAttributes
};
// add protocolProfileBehavior property from item to the request
util.addProtocolProfileBehavior(item, request);
// only include the dataDisabled flag if truthy
if (req && req.body && _.has(req.body, 'disabled') && (req.body.disabled || retainEmpty)) {
request.dataDisabled = Boolean(req.body.disabled);
}
description = item.request && self.description(item.request.description);
// Prevent empty request descriptions from showing up in the converted result, keeps collections clean.
if (description) { request.description = description; }
else if (retainEmpty) { request.description = null; }
(auth || (auth === null)) && (request.auth = auth);
events && events.length && (request.events = events);
variables && variables.length && (request.variables = variables);
_.forEach(units, function (unit) {
request[unit] = self[unit](item);
});
// description transformations for v2 to v1
urlObj && (request.pathVariableData = _.map(urlObj.variables || urlObj.variable, function (v) {
var result = { key: v.key || v.id, value: v.value };
// Prevent empty path variable descriptions from showing up in converted results, keeps collections clean.
if (v.description) { result.description = self.description(v.description); }
else if (retainEmpty) { result.description = null; }
return result;
}));
urlObj && (request.queryParams = _.map(urlObj.query, function (queryParam) {
// Prevents empty query param descriptions from showing up in the result, keeps collections clean.
queryParam.description = self.description(queryParam.description);
_.has(queryParam, 'disabled') && (queryParam.enabled = !queryParam.disabled);
delete queryParam.disabled;
return queryParam;
}));
// item truthiness is already validated by this point
request.headerData = _.map(item.request && (item.request.headers || item.request.header), function (header) {
// Prevents empty query param descriptions from showing up in the result, keeps collections clean.
header.description = self.description(header.description);
_.has(header, 'disabled') && (header.enabled = !header.disabled);
delete header.disabled;
return header;
});
return request;
},
/**
* Creates a V1 compatible array of requests from a Postman V2 collection.
*
* @param {Object} collectionV2 - The v2 collection to derive v1 requests from.
* @returns {Object[]} - The list of v1 request objects.
*/
requests: function (collectionV2) {
var self = this,
requests = [],
info = collectionV2 && collectionV2.info,
id = info && (info.id || info._postman_id) || collectionV2.id;
_.forEach(self.extractItems(collectionV2), function (item) {
var requestV1 = self.request(item, id);
requests.push(requestV1);
});
return requests;
},
/**
* Creates a V1 compatible array of solo requestIds from a Postman collection V2
*
* @param {Object} collectionV2 - The v2 collection to be used for request order derivation.
* @returns {Object[]} - The request order for the resultant v1 collection.
*/
order: function (collectionV2) {
var itemArray = collectionV2.items || collectionV2.item,
allItems = _.isArray(itemArray) ? itemArray : [itemArray];
// eslint-disable-next-line lodash/prefer-compact
return _.filter(_.map(allItems, function (item) {
if (!item) { return; }
var isFolder = (item.items || item.item);
if (!isFolder) {
return item.id || item._postman_id;
}
}));
},
/**
* Creates a V1 compatible array of folder orders from a Postman collection V2
*
* @param {Object} folderOrCollection - The object to derive folder order details from.
* @returns {Object[]} - The list of folder ids that indicate the order.
*/
folders_order: function (folderOrCollection) {
var itemArray = folderOrCollection.items || folderOrCollection.item,
allItems = _.isArray(itemArray) ? itemArray : [itemArray];
// eslint-disable-next-line lodash/prefer-compact
return _.filter(_.map(allItems, function (item) {
if (!item) { return; }
var isFolder = (item.items || item.item);
if (isFolder) {
return item.id || item._postman_id;
}
}));
},
/**
* Creates an array of V1 compatible folders from a V2 collection
*
* @param {Object} collectionV2 - The v2 collection to derive folder structure information from.
* @returns {Object[]} - The list of folder definitions.
*/
folders: function (collectionV2) {
var self = this,
retainEmpty = self.options.retainEmptyValues;
return _.map(self.extractFolderItems(collectionV2), function (folder) {
if (!folder) { return; }
var folderItems = folder.items || folder.item,
description = self.description(folder.description),
auth = self.auth(folder),
events = self.events(folder),
variables = self.variables(folder, { retainIds: self.options.retainIds }),
result = {
// do not attempt to regenerate folder id here when `retainIds` is set to false
// if id is changed here the parent's `folder_order` also needs to be changed
// that can't be done yet
id: folder.id || folder._postman_id || util.uid(),
name: folder.name,
// eslint-disable-next-line lodash/prefer-compact
order: _.filter(_.map(folderItems, function (f) {
if (!f) { return; }
var isFolder = (f.items || f.item);
return !isFolder && (f.id || f._postman_id);
})),
folders_order: self.folders_order(folder)
};
((auth && auth.type) || (auth === null)) && (result.auth = auth);
events && events.length && (result.events = events);
variables && variables.length && (result.variables = variables);
util.addProtocolProfileBehavior(folder, result);
// Prevent empty folder descriptions from showing up in the result, keeps collections clean.
if (description) { result.description = description; }
else if (retainEmpty) { result.description = null; }
return result;
});
},
/**
* Creates the v1.0.0 compatible description string from the v2.0.0 description format.
*
* @param {Object} descriptionV2 - The v2 style description to be converted
*
* @returns {String} - The resultant v1 description.
*/
description: function (descriptionV2) {
var description,
retainEmpty = this.options.retainEmptyValues;
description = _.isObject(descriptionV2) ? descriptionV2.content : descriptionV2;
if (description) { return description; }
else if (retainEmpty) { return null; }
}
});
module.exports = {
input: '2.0.0',
output: '1.0.0',
Builders: Builders,
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} request - The request to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertSingle: function (request, options, callback) {
var builders = new Builders(options),
clone = _.cloneDeep(request),
converted,
err;
try {
clone = v2Common.populateIds(clone);
converted = builders.request(clone);
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} response - The response to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try {
converted = builders.response(_.cloneDeep(response));
}
catch (e) {
err = e;
}
if (callback) {
return callback(err, converted);
}
if (err) {
throw err;
}
return converted;
},
/**
* Converts a V2 collection to a V1 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
events,
variables,
builders = new Builders(options),
authOptions = { excludeNoauth: true },
varOpts = options && { fallback: options.env, retainIds: options.retainIds },
units = ['order', 'folders_order', 'folders', 'requests'],
id = _.get(collection, 'info._postman_id') || _.get(collection, 'info.id'),
info = collection && collection.info,
newCollection = {
id: id && options && options.retainIds ? id : util.uid(),
name: info && info.name
};
// ensure that each item has an ID
collection = v2Common.populateIds(collection);
try {
// eslint-disable-next-line max-len
newCollection.description = builders.description(info && info.description);
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(events = builders.events(collection)) && (newCollection.events = events);
(variables = builders.variables(collection, varOpts)) && (newCollection.variables = variables);
util.addProtocolProfileBehavior(collection, newCollection);
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
}
catch (e) {
if (callback) {
return callback(e, null);
}
throw e;
}
if (callback) {
return callback(null, newCollection);
}
return newCollection;
}
};

View File

@@ -0,0 +1,129 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
util = require('../../util'),
inherits = require('inherits'),
v2Common = require('../../common/v2'),
BaseBuilders = require('../v2.0.0/converter-v2-to-v1').Builders,
Builders;
inherits(Builders = function () {
Builders.super_.apply(this, arguments);
}, BaseBuilders);
_.assign(Builders.prototype, {
/**
* Converts arrays of v2.1 style auth params to their v1.0.0 equivalent objects.
*
* @param {Object} entity - A v2.1 compliant wrapped auth manifest.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to null.
* @returns {Object} - A v1 compliant set of auth helper attributes.
*/
auth: function (entity, options) {
return util.sanitizeAuthArray(entity, options);
}
});
module.exports = {
input: '2.1.0',
output: '1.0.0',
Builders: Builders,
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} request - The request to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertSingle: function (request, options, callback) {
var err,
converted,
clone = _.cloneDeep(request),
builders = new Builders(options);
try {
clone = v2Common.populateIds(clone);
converted = builders.request(clone);
}
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a single V2 item to a V1 request.
*
* @param {Object} response - The response to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convertResponse: function (response, options, callback) {
var builders = new Builders(options),
converted,
err;
try { converted = builders.response(_.cloneDeep(response)); }
catch (e) { err = e; }
if (callback) { return callback(err, converted); }
if (err) { throw err; }
return converted;
},
/**
* Converts a V2 collection to a V1 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The collection to be converted.
* @param {Object} options - The set of options for request conversion.
* @param {Function} callback - The function to be invoked after conversion has completed.
*/
convert: function (collection, options, callback) {
collection = _.cloneDeep(collection);
var auth,
events,
variables,
builders = new Builders(options),
authOptions = { excludeNoauth: true },
units = ['order', 'folders_order', 'folders', 'requests'],
varOpts = options && { fallback: options.env, retainIds: options.retainIds },
id = _.get(collection, 'info._postman_id') || _.get(collection, 'info.id'),
info = collection && collection.info,
newCollection = {
id: id && options && options.retainIds ? id : util.uid(),
name: info && info.name
};
// ensure that each item has an id
collection = v2Common.populateIds(collection);
try {
// eslint-disable-next-line max-len
newCollection.description = builders.description(info && info.description);
(auth = builders.auth(collection, authOptions)) && (newCollection.auth = auth);
(events = builders.events(collection)) && (newCollection.events = events);
(variables = builders.variables(collection, varOpts)) && (newCollection.variables = variables);
util.addProtocolProfileBehavior(collection, newCollection);
units.forEach(function (unit) {
newCollection[unit] = builders[unit](collection);
});
}
catch (e) {
if (callback) { return callback(e, null); }
throw e;
}
if (callback) { return callback(null, newCollection); }
return newCollection;
}
};

View File

@@ -0,0 +1,97 @@
var semver = require('semver'),
util = require('./util'),
converter = require('./converters'),
normalizers = require('./normalizers');
module.exports = {
/**
* Converts a Collection between different versions, based on the given input.
*
* @param {Object} collection - The collection object to be converted.
* @param {Object} options - The set of conversion options.
* @param {String} options.outputVersion - The version to convert to.
* @param {String} options.inputVersion - The version to convert from.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
*/
convert: function (collection, options, callback) {
if (!options.outputVersion || !semver.valid(options.outputVersion, true)) {
return callback(new Error('Output version not specified or invalid: ' + options.outputVersion));
}
if (!options.inputVersion || !semver.valid(options.inputVersion, true)) {
return callback(new Error('Input version not specified or invalid: ' + options.inputVersion));
}
return converter.convert(collection, options, callback);
},
normalize: normalizers.normalize,
normalizeSingle: normalizers.normalizeSingle,
normalizeResponse: normalizers.normalizeResponse,
/**
* Export the utilities
*/
util: util,
/**
* Checks whether the given object is a v1 collection
*
* @param {Object} object - The Object to check for v1 collection compliance.
* @returns {Boolean} - A boolean result indicating whether or not the passed object was a v1 collection.
*/
isv1: function (object) {
return Boolean(object && object.name && object.order && object.requests);
},
/**
* Checks whether the given object is a v2 collection
*
* @param {Object} object - The Object to check for v2 collection compliance.
* @returns {Boolean} - A boolean result indicating whether or not the passed object was a v2 collection.
*/
isv2: function (object) {
return Boolean(object && object.info && object.info.schema);
},
/**
* Converts a single V1 request to a V2 Item, or a V2 item to a single V1 request.
*
* @param {Object} object - A V1 request or a V2 item.
* @param {Object} options - The set of options for response conversion.
* @param {String} options.outputVersion - The version to convert to.
* @param {String} options.inputVersion - The version to convert from.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
*/
convertSingle: function (object, options, callback) {
if (!options.outputVersion || !semver.valid(options.outputVersion, true)) {
return callback(new Error('Output version not specified or invalid: ' + options.outputVersion));
}
if (!options.inputVersion || !semver.valid(options.inputVersion, true)) {
return callback(new Error('Input version not specified or invalid: ' + options.inputVersion));
}
return converter.convertSingle(object, options, callback);
},
/**
* Converts a single V1 request to a V2 Item, or a V2 item to a single V1 request.
*
* @param {Object} object - A V1 request or a V2 item.
* @param {Object} options - The set of options for response conversion.
* @param {String} options.outputVersion - The version to convert to.
* @param {String} options.inputVersion - The version to convert from.
* @param {Function} callback - The function invoked to mark the completion of the conversion process.
*/
convertResponse: function (object, options, callback) {
if (!options.outputVersion || !semver.valid(options.outputVersion, true)) {
return callback(new Error('Output version not specified or invalid: ' + options.outputVersion));
}
if (!options.inputVersion || !semver.valid(options.inputVersion, true)) {
return callback(new Error('Input version not specified or invalid: ' + options.inputVersion));
}
return converter.convertResponse(object, options, callback);
}
};

View File

@@ -0,0 +1,79 @@
/* eslint-disable object-shorthand */
var semver = require('semver'),
// @todo: Add support for more normalizers
normalizers = {
'1.0.0': require('./v1')
};
module.exports = {
/**
* Accepts the arguments for normalization and invokes the appropriate normalizer with them.
*
* @param {Object} collection - The plain collection JSON to be normalized.
* @param {Object} options - A set of options for the current normalization.
* @param {String} options.normalizeVersion - The base collection schema version for which to normalize.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function invoked to indicate the completion of the normalization process.
* @returns {*}
*/
normalize: function (collection, options, callback) {
var version;
if (!options || !(version = semver.valid(options.normalizeVersion, true)) || !normalizers[version]) {
return callback(new Error('Version not specified or invalid: ' + options.normalizeVersion));
}
return normalizers[version].normalize(collection, options, callback);
},
/**
* Normalizes a single request or item as per the provided version.
*
* @param {Object} object - The entity to be normalized.
* @param {Object} options - The set of options to be applied to the current normalization.
* @param {String} options.normalizeVersion - The base collection schema version for which to normalize.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - The function invoked when the normalization has completed.
*/
normalizeSingle: function (object, options, callback) {
var version;
if (!options || !(version = semver.valid(options.normalizeVersion, true)) || !normalizers[version]) {
return callback(new Error('Version not specified or invalid: ' + options.normalizeVersion));
}
return normalizers[version].normalizeSingle(object, options, callback);
},
/**
* Normalizes a single response as per the provided version.
*
* @param {Object} response - The response to be normalized.
* @param {Object} options - The set of options to be applied to the current normalization.
* @param {String} options.normalizeVersion - The base collection schema version for which to normalize.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - The function invoked when the normalization has completed.
*/
normalizeResponse: function (response, options, callback) {
var version;
if (!options || !(version = semver.valid(options.normalizeVersion, true)) || !normalizers[version]) {
return callback(new Error('Version not specified or invalid: ' + options.normalizeVersion));
}
return normalizers[version].normalizeResponse(response, options, callback);
}
};

View File

@@ -0,0 +1,693 @@
/* eslint-disable object-shorthand */
var _ = require('lodash').noConflict(),
v1Common = require('../common/v1'),
v2Common = require('../common/v2'),
util = require('../util'),
url = require('../url'),
Builders = function (options) {
this.options = options || {};
},
script = function (entityV1, listen, key) {
return {
listen: listen,
script: {
type: 'text/javascript',
exec: _.isString(entityV1[key]) ? entityV1[key].split('\n') : entityV1[key]
}
};
},
authIdMap = {
apikey: 'apikeyAuth',
awsSigV4: 'awsSigV4',
basic: 'basicAuth',
bearer: 'bearerAuth',
digest: 'digestAuth',
hawk: 'hawkAuth',
ntlm: 'ntlmAuth',
oAuth1: 'oAuth1',
oAuth2: 'oAuth2'
},
/**
* Normalizes `description` field of an entity.
* If `description` field is absent, this is a no-op.
* Will mutate the entity.
*
* @param {Object} entity - Wrapper object, possibly containing a description field
* @param {Object} builder - Builder instance that will be called to perform normalization
* @param {Object} utilOptions - Options to be passed to util fn
*/
normalizeDescription = function (entity, builder, utilOptions) {
var retainEmptyValues = _.get(utilOptions, 'retainEmptyValues');
if (_.has(entity, 'description')) {
entity.description = builder.description(entity.description);
}
util.cleanEmptyValue(entity, 'description', retainEmptyValues);
return entity;
};
_.assign(Builders.prototype, {
/**
* Normalizes inherited v1 auth manifests.
*
* @param {Object} entityV1 - A v1 compliant wrapped auth manifest.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.includeNoauth=false] - When set to true, noauth is set to ''.
*
* @returns {Object} - A v1 compliant set of auth helper attributes.
*/
auth: function (entityV1, options) {
if (!entityV1) { return; }
var auth,
params,
mapper,
currentHelper,
helperAttributes,
prioritizeV2 = this.options.prioritizeV2;
// if prioritize v2 is true, use auth as the source of truth
if (util.notLegacy(entityV1, 'auth') || (entityV1.auth && prioritizeV2)) {
return util.sanitizeAuthArray(entityV1, options);
}
if ((entityV1.currentHelper === null) || (entityV1.currentHelper === 'normal')) { return null; }
currentHelper = entityV1.currentHelper;
helperAttributes = entityV1.helperAttributes;
// if noDefaults is false and there is no currentHelper, bail out
if (!(currentHelper || this.options.noDefaults)) { return; }
// if there is a currentHelper without helperAttributes, bail out.
if (currentHelper && !helperAttributes) { return this.options.noDefaults ? undefined : null; }
!currentHelper && (currentHelper = authIdMap[helperAttributes && helperAttributes.id]);
auth = { type: v1Common.authMap[currentHelper] };
mapper = util.authMappersFromLegacy[currentHelper];
// @todo: Change this to support custom auth helpers
mapper && helperAttributes && (params = mapper(helperAttributes)) && (auth[auth.type] = params);
return util.authMapToArray({ auth: auth }, options);
},
/**
* Normalizes v1 collection events.
*
* @param {Object} entityV1 - The v1 entity to be normalized.
* @returns {Array|null} - The normalized events.
*/
events: function (entityV1) {
if (!entityV1) { return; }
if ((util.notLegacy(entityV1, 'event') || this.options.prioritizeV2) && !_.isEmpty(entityV1.events)) {
// @todo: Improve this to order prerequest events before test events
_.forEach(entityV1.events, function (event) {
!event.listen && (event.listen = 'test');
if (event.script) {
!event.script.type && (event.script.type = 'text/javascript');
// @todo: Add support for src
_.isString(event.script.exec) && (event.script.exec = event.script.exec.split('\n'));
}
});
return entityV1.events;
}
var events = [];
entityV1.preRequestScript && events.push(script(entityV1, 'prerequest', 'preRequestScript'));
entityV1.tests && events.push(script(entityV1, 'test', 'tests'));
if (events.length) { return events; }
// retain `null` events
if (entityV1.events === null) { return null; }
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} options - The set of options for the current variable transformation.
* @param {?Object} options.fallback - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} options.noDefaults - When set to true, no defaults are applied.
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
variables: function (entity, options) {
var self = this,
// Use builder's own options if override is not requested
results = util.handleVars(entity, options || this.options);
// Normalize descriptions that may have been passed in as objects
results = _.map(results, function (item) {
return normalizeDescription(item, self, options || self.options);
});
if (results.length) {
return results;
}
},
/**
* Sanitizes request v1 data.
*
* @param {Object} requestV1 - The wrapper v1 request object around the data list to be sanitized.
* @returns {Object[]} - The normalized list of request body parameters.
*/
data: function (requestV1) {
if (!requestV1) { return; }
var self = this,
mode = requestV1.dataMode,
noDefaults = this.options.noDefaults,
retainEmptyValues = this.options.retainEmptyValues;
if ((!mode || mode === 'binary') && !noDefaults) {
return retainEmptyValues ? [] : undefined;
}
if (!requestV1.data) { return; }
_.isArray(requestV1.data) && _.forEach(requestV1.data, function (datum) {
if (datum.type === 'file' && (_.has(datum, 'value') || !noDefaults)) {
datum.value = (_.isString(datum.value) || _.isArray(datum.value)) ? datum.value : null;
}
normalizeDescription(datum, self, self.options);
});
return requestV1.data;
},
/**
* Normalizes a list of header data from the incoming raw v1 request.
*
* @param {Object} requestV1 - The raw v1 request object.
* @returns {Object[]} - The normalized list of header datum values.
*/
headerData: function (requestV1) {
var self = this,
normalizedHeaderData;
if (!requestV1) { return; }
if (requestV1.headers && _.isEmpty(requestV1.headerData)) {
// this converts a newline concatenated string of headers to an array, so there are no descriptions
return v1Common.parseHeaders(requestV1.headers, true);
}
// however, if non empty headerData already exists, sanitize it.
normalizedHeaderData = _.map(requestV1.headerData, function (entity) {
return normalizeDescription(entity, self, self.options);
});
if (normalizedHeaderData.length) {
return normalizedHeaderData;
}
},
queryParams: function (requestV1) {
if (!requestV1) { return; }
var self = this,
normalizedQueryParams,
urlObj;
if (!requestV1.queryParams) {
return requestV1.url && (urlObj = url.parse(requestV1.url)) && urlObj.query;
}
normalizedQueryParams = _.map(requestV1.queryParams, function (entity) {
return normalizeDescription(entity, self, self.options);
});
if (normalizedQueryParams.length) {
return normalizedQueryParams;
}
},
/**
* Facilitates sanitized variable transformations across all levels for v1 collection normalization.
*
* @param {Object} entity - The wrapper object containing variable definitions.
* @param {?Object} [options] - The set of options for the current variable transformation.
* @param {?Object} [options.fallback] - The set of fallback values to be applied when no variables exist.
* @param {?Boolean} [options.noDefaults] - When set to true, no defaults are applied.
* @param {?Boolean} [options.retainEmptyValues] - When set to true, empty values are set to null instead of being
* removed.
* @param {?Boolean} [options.retainIds] - When set to true, ids are left as is.
* @returns {Object[]} - The set of sanitized variables.
*/
pathVariableData: function (entity, options) {
var self = this,
results = util.handleVars(entity, options, { isV1: true });
// Normalize descriptions that may have been passed in as objects
results = _.map(results, function (item) {
return normalizeDescription(item, self, self.options);
});
if (results.length) {
return results;
}
},
/**
* Normalizes a potentially raw v1 request object.
*
* @param {Object} requestV1 - The potentially raw v1 request object.
* @param {?String} collectionId - A unique identifier for the v1 collection.
* @param {?Boolean} [skipResponses=false] - When set to true, saved responses will be excluded from the result..
* @returns {Object} - The normalized v1 request object.
*/
request: function (requestV1, collectionId, skipResponses) {
if (!requestV1) { return; }
var map,
auth,
tests,
events,
mapper,
variables,
self = this,
helperAttributes,
preRequestScript,
options = this.options,
noDefaults = options.noDefaults,
retainEmpty = options.retainEmptyValues,
varOpts = { noDefaults: options.noDefaults, retainIds: options.retainIds },
units = ['queryParams', 'pathVariableData', 'headerData', 'data'];
if (!skipResponses) {
units.push('responses');
units.push('responses_order');
}
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((options.retainIds && requestV1.id) || options.noDefaults) && (requestV1.id = util.uid());
normalizeDescription(requestV1, self, self.options);
units.forEach(function (unit) {
var result = self[unit](requestV1, self.options);
result && (requestV1[unit] = result);
});
if (requestV1.dataDisabled) { requestV1.dataDisabled = true; }
else if (retainEmpty) { requestV1.dataDisabled = false; }
else { delete requestV1.dataDisabled; }
// remove invalid protocolProfileBehavior property from requestV1
!util.addProtocolProfileBehavior(requestV1) && delete requestV1.protocolProfileBehavior;
collectionId && !noDefaults && (requestV1.collectionId = collectionId);
// normalized v1 requests should not have falsy helperAttributes or currentHelper
if (_.has(requestV1, 'currentHelper')) {
(requestV1.currentHelper === 'normal') && (requestV1.currentHelper = null);
if (!requestV1.currentHelper) {
(requestV1.currentHelper !== null) && (requestV1.currentHelper = null);
// @todo: Should currentHelper be recreated from helperAttributes.id if falsy?
requestV1.helperAttributes = null;
}
}
auth = self.auth(requestV1);
if (auth) {
requestV1.auth = auth;
if (_.has(requestV1, 'helperAttributes') && !requestV1.currentHelper) {
requestV1.currentHelper = authIdMap[auth.type];
}
}
else if (auth === null) { // eslint-disable-line security/detect-possible-timing-attacks
requestV1.auth = requestV1.currentHelper = requestV1.helperAttributes = null;
}
else { delete requestV1.auth; }
events = self.events(requestV1);
if (events || events === null) {
requestV1.events = events;
}
else {
delete requestV1.events;
}
variables = self.variables(requestV1, varOpts);
if (variables) {
requestV1.variables = variables;
}
else {
delete requestV1.variables;
}
if (requestV1.auth && (util.notLegacy(requestV1, 'auth') || options.prioritizeV2)) {
requestV1.currentHelper = v2Common.authMap[requestV1.auth.type];
(requestV1.currentHelper === null) && (requestV1.helperAttributes = null);
mapper = util.authMappersFromCurrent[requestV1.currentHelper];
if (mapper) {
(map = util.authArrayToMap(requestV1)) && (helperAttributes = mapper(map[requestV1.auth.type]));
helperAttributes && (requestV1.helperAttributes = helperAttributes);
}
}
if (requestV1.events && (util.notLegacy(requestV1, 'event') || options.prioritizeV2)) {
tests = preRequestScript = '';
_.forEach(requestV1.events, function (event) {
var exec = event && event.script && event.script.exec;
if (!_.isArray(exec)) { return; }
if (event.listen === 'prerequest') {
preRequestScript += exec.join('\n');
}
else if (event.listen === 'test') {
tests += exec.join('\n');
}
});
requestV1.preRequestScript = preRequestScript ? preRequestScript : null;
requestV1.tests = tests ? tests : null;
}
// prune
['preRequestScript', 'tests'].forEach(function (script) {
if (_.has(requestV1, script) && !requestV1[script] && requestV1[script] !== null) {
delete requestV1[script];
}
});
return requestV1;
},
/**
* Normalizes a potentially raw v1 response object.
*
* @param {Object} responseV1 - The potentially raw v1 response object.
* @returns {Object} - The normalized v1 response object.
*/
response: function (responseV1) {
var self = this;
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((self.options.retainIds && responseV1.id) || self.options.noDefaults) && (responseV1.id = util.uid());
// the true in the next line ensures that we don't recursively go on processing responses in a request.
responseV1.request = self.request(responseV1.request, undefined, true);
!responseV1.language && (responseV1.language = 'Text');
!responseV1.previewType && (responseV1.previewType = 'html');
_.isEmpty(responseV1.cookies) && (delete responseV1.cookies);
return responseV1;
},
responses: function (requestV1) {
if (_.isEmpty(requestV1 && requestV1.responses)) { return; }
var self = this;
requestV1.responses.forEach(function (response) {
self.response(response);
});
return requestV1.responses;
},
/**
* Normalizes a request order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of folder ids.
* @returns {Array} - The normalized list of folder ids.
*/
order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.order);
},
/**
* Normalizes a folder order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of folder ids.
* @returns {Array} - The normalized list of folder ids.
*/
folders_order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.folders_order);
},
/**
* Normalizes a response order list.
*
* @param {Object} entityV1 - An object containing a potentially raw list of response ids.
* @returns {Array} - The normalized list of response ids.
*/
responses_order: function (entityV1) {
return !this.options.noDefaults && _.compact(entityV1 && entityV1.responses_order);
},
/**
* Normalizes a potentially raw v1 folders list.
*
* @param {Object} collectionV1 - The potentially raw v1 collection object.
* @returns {Object[]} - The normalized v1 collection folders list.
*/
folders: function (collectionV1) {
if (_.isEmpty(collectionV1 && collectionV1.folders)) { return; }
var auth,
events,
variables,
self = this,
order,
foldersOrder,
retainEmpty = self.options.retainEmptyValues,
varOpts = { noDefaults: self.options.noDefaults, retainIds: self.options.retainIds };
_.forEach(collectionV1.folders, function (folder) {
if (!folder) { return; }
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((self.options.retainIds && folder.id) || self.options.noDefaults) && (folder.id = util.uid());
folder.description = self.description(folder.description);
util.cleanEmptyValue(folder, 'description', retainEmpty);
// remove invalid protocolProfileBehavior property
!util.addProtocolProfileBehavior(folder) && delete folder.protocolProfileBehavior;
auth = self.auth(folder);
!_.isEmpty((order = self.order(folder))) && (folder.order = order);
!_.isEmpty((foldersOrder = self.folders_order(folder))) && (folder.folders_order = foldersOrder);
(auth || (auth === null)) && (folder.auth = auth);
(events = self.events(folder)) && (folder.events = events);
(variables = self.variables(folder, varOpts)) && (folder.variables = variables);
});
return _.compact(collectionV1.folders);
},
/**
* Normalizes a potentially raw v1 request object.
*
* @param {Object} collectionV1 - The potentially raw v1 collection object.
* @returns {Object[]|*} - The normalized v1 request list.
*/
requests: function (collectionV1) {
if (_.isEmpty(collectionV1 && collectionV1.requests)) { return; }
var self = this;
collectionV1.requests.forEach(function (request) {
self.request(request);
});
return _.compact(collectionV1.requests);
},
/**
* Creates the v1.0.0 compatible description string.
*
* @param {Object} maybeObjectDescription - The description to be converted
*
* @returns {String} - The resultant v1 description.
*/
description: function (maybeObjectDescription) {
var description,
retainEmpty = _.get(this.options, 'retainEmptyValues'),
createDefaultValue = !_.get(this.options, 'noDefaults', false);
if (_.isObject(maybeObjectDescription)) {
description = _.toString(_.get(maybeObjectDescription, 'content'));
}
else {
description = maybeObjectDescription;
}
if (description) {
return description;
}
else if (description === undefined && createDefaultValue) {
return null;
}
else if (_.isEmpty(description) && retainEmpty) {
return null;
}
return undefined;
}
});
module.exports = {
/**
* Normalizes a single v1 request.
*
* @param {Object} request - The v1 request to be normalized.
* @param {Object} options - The set of options for the current normalization.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function that is invoked when the normalization has completed.
* @returns {*}
*/
normalizeSingle: function (request, options, callback) {
var err,
normalized,
builders = new Builders(options);
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the request.
options && !options.mutate && (request = _.cloneDeep(request));
try { normalized = builders.request(request); }
catch (e) { err = e; }
if (callback) { return callback(err, normalized); }
if (err) { throw err; }
return normalized;
},
/**
* Normalizes a single v1 response.
*
* @param {Object} response - The v1 request to be normalized.
* @param {Object} options - The set of options for the current normalization.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function that is invoked when the normalization has completed.
* @returns {*}
*/
normalizeResponse: function (response, options, callback) {
var err,
normalized,
builders = new Builders(options);
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the response.
options && !options.mutate && (response = _.cloneDeep(response));
try { normalized = builders.response(response); }
catch (e) { err = e; }
if (callback) { return callback(err, normalized); }
if (err) { throw err; }
return normalized;
},
/**
* Converts a V1 collection to a V2 collection (performs ID replacement, etc as necessary).
*
* @param {Object} collection - The v1 collection to be normalized.
* @param {Object} options - The options for the current normalization sequence.
* @param {?Boolean} [options.mutate=false] - When set to true, normalization is done in place.
* @param {?Boolean} [options.noDefaults=false] - When set to true, sensible defaults are not added.
* @param {?Boolean} [options.prioritizeV2=false] - When set to true, v2 style properties are checked first.
* @param {?Boolean} [options.retainEmptyValues=false] - When set to true, empty values are set to '' instead of
* being removed.
* @param {Function} callback - A function invoked to indicate that the normalization has completed.
* @returns {*}
*/
normalize: function (collection, options, callback) {
// At this stage, mutate will not be passed ordinarily. Hence, the falsy nature of options.mutate can be used
// to selectively clone the collection.
options && !options.mutate && (collection = _.cloneDeep(collection));
var auth,
authOptions = { excludeNoauth: true },
builders = new Builders(options),
units = ['events', 'variables', 'order', 'folders_order', 'folders', 'requests'];
// if noDefaults is true, do not replace the id
// else
// if id is falsy, replace the id
// if retainIds is false, replace the id
!((options.retainIds && collection.id) || options.noDefaults) && (collection.id = util.uid());
normalizeDescription(collection, builders, options);
// remove invalid protocolProfileBehavior property
!util.addProtocolProfileBehavior(collection) && delete collection.protocolProfileBehavior;
try {
auth = builders.auth(collection, authOptions);
if (auth || (options.retainEmptyValues && auth === null)) {
collection.auth = auth;
}
else {
delete collection.auth;
}
units.forEach(function (unit) {
var result,
_options;
if (unit === 'variables') {
_options = { retainIds: options.retainIds, noDefaults: options.noDefaults };
}
if (!_.isEmpty(result = builders[unit](collection, _options)) || (unit === 'folders')) {
collection[unit] = result;
}
});
}
catch (e) {
if (callback) { return callback(e, null); }
throw e;
}
if (callback) { return callback(null, collection); }
return collection;
}
};

663
node_modules/postman-collection-transformer/lib/url.js generated vendored Normal file
View File

@@ -0,0 +1,663 @@
/**
* @fileoverview
*
* Copied the URL parser and unparser from the SDK.
* @todo Move this into it's own separate module, and make the SDK and transformer depend on it.
*/
const _ = require('lodash').noConflict(),
E = '',
HASH = '#',
SLASH = '/',
COLON = ':',
EQUALS = '=',
AMPERSAND = '&',
AUTH_SEPARATOR = '@',
QUERY_SEPARATOR = '?',
DOMAIN_SEPARATOR = '.',
PROTOCOL_SEPARATOR = '://',
PROTOCOL_SEPARATOR_WITH_BACKSLASH = ':\\\\',
STRING = 'string',
FUNCTION = 'function',
SAFE_REPLACE_CHAR = '_',
CLOSING_SQUARE_BRACKET = ']',
URL_PROPERTIES_ORDER = ['protocol', 'auth', 'host', 'port', 'path', 'query', 'hash'],
REGEX_HASH = /#/g,
REGEX_EQUALS = /=/g, // eslint-disable-line no-div-regex
REGEX_AMPERSAND = /&/g,
REGEX_EXTRACT_VARS = /{{[^{}]*[.:/?#@&\]][^{}]*}}/g,
REGEX_EXTRACT_VARS_IN_PARAM = /{{[^{}]*[&#=][^{}]*}}/g,
/**
* Percent encode reserved chars (&, = and #) in the given string.
*
* @private
* @param {String} str - String to encode
* @param {Boolean} encodeEquals - Encode '=' if true
* @returns {String} - Encoded string
*/
encodeReservedChars = function (str, encodeEquals) {
if (!str) {
return str;
}
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(AMPERSAND) !== -1 && (str = str.replace(REGEX_AMPERSAND, '%26'));
// eslint-disable-next-line lodash/prefer-includes
str.indexOf(HASH) !== -1 && (str = str.replace(REGEX_HASH, '%23'));
// eslint-disable-next-line lodash/prefer-includes
encodeEquals && str.indexOf(EQUALS) !== -1 && (str = str.replace(REGEX_EQUALS, '%3D'));
return str;
},
/**
* Normalize the given param string by percent-encoding the reserved chars
* such that it won't affect the re-parsing.
*
* @note `&`, `=` and `#` needs to be percent-encoded otherwise re-parsing
* the same URL string will generate different output
*
* @private
* @param {String} str - Parameter string to normalize
* @param {Boolean} encodeEquals - If true, encode '=' while normalizing
* @returns {String} - Normalized param string
*/
normalizeParam = function (str, encodeEquals) {
// bail out if the given sting is null or empty
if (!(str && typeof str === STRING)) {
return str;
}
// bail out if the given string does not include reserved chars
// eslint-disable-next-line lodash/prefer-includes
if (str.indexOf(AMPERSAND) === -1 && str.indexOf(HASH) === -1) {
// eslint-disable-next-line lodash/prefer-includes
if (!(encodeEquals && str.indexOf(EQUALS) !== -1)) {
return str;
}
}
var normalizedString = '',
pointer = 0,
variable,
match,
index;
// find all the instances of {{<variable>}} which includes reserved chars
while ((match = REGEX_EXTRACT_VARS_IN_PARAM.exec(str)) !== null) {
variable = match[0];
index = match.index;
// [pointer, index) string is normalized + the matched variable
normalizedString += encodeReservedChars(str.slice(pointer, index), encodeEquals) + variable;
// update the pointer
pointer = index + variable.length;
}
// whatever left in the string is normalized as well
if (pointer < str.length) {
normalizedString += encodeReservedChars(str.slice(pointer), encodeEquals);
}
return normalizedString;
},
/**
* Unparses a single query param into a string.
*
* @private
* @param {Object} obj - The query parameter object to be unparsed.
* @returns {String} - The unparsed query string.
*/
unparseQueryParam = function (obj) {
if (!obj) { return E; }
var key = obj.key,
value = obj.value,
result;
if (typeof key === STRING) {
result = normalizeParam(key, true);
}
else {
result = E;
}
if (typeof value === STRING) {
result += EQUALS + normalizeParam(value);
}
return result;
},
/**
* Parses a single query param string into an object.
*
* @private
* @param {String} param - The query parameter string to be parsed.
* @returns {Object} - The parsed query object.
*/
parseQueryParam = function (param) {
if (param === E) {
return { key: null, value: null };
}
var index = _.indexOf(param, EQUALS);
if (index < 0) {
return { key: param, value: null };
}
return {
key: param.substr(0, index),
value: param.substr(index + 1)
};
};
/**
* Tracks replacements done on a string and expose utility to patch replacements.
*
* @note due to performance reasons, it doesn't store the original string or
* perform ops on the string.
*
* @private
* @constructor
*/
function ReplacementTracker () {
this.replacements = [];
this._offset = 0;
this._length = 0;
}
/**
* Add new replacement to track.
*
* @param {String} value - value being replaced
* @param {Number} index - index of replacement
*/
ReplacementTracker.prototype.add = function (value, index) {
this.replacements.push({
value: value,
index: index - this._offset
});
this._offset += value.length - 1; // - 1 replaced character
this._length++;
};
/**
* Returns the total number of replacements.
*
* @returns {Number}
*/
ReplacementTracker.prototype.count = function () {
return this._length;
};
/**
* Finds the lower index of replacement position for a given value using inexact
* binary search.
*
* @param {Number} index - index to search in replacements
* @returns {Number}
*/
ReplacementTracker.prototype._findLowerIndex = function (index) {
var length = this.count(),
start = 0,
end = length - 1,
mid;
while (start <= end) {
mid = (start + end) >> 1; // divide by 2
if (this.replacements[mid].index >= index) {
end = mid - 1;
}
else {
start = mid + 1;
}
}
return start >= length ? -1 : start;
};
/**
* Patches a given string by apply all the applicable replacements done in the
* given range.
*
* @param {String} input - string to apply replacements on
* @param {Number} beginIndex - index from where to apply replacements in input
* @param {Number} endIndex - index until where to apply replacements in input
* @returns {String}
*/
ReplacementTracker.prototype._applyInString = function (input, beginIndex, endIndex) {
var index,
replacement,
replacementIndex,
replacementValue,
offset = 0,
length = this.count();
// bail out if no replacements are done in the given range OR empty string
if (!input || (index = this._findLowerIndex(beginIndex)) === -1) {
return input;
}
do {
replacement = this.replacements[index];
replacementIndex = replacement.index;
replacementValue = replacement.value;
// bail out if all the replacements are done in the given range
if (replacementIndex >= endIndex) {
break;
}
replacementIndex = offset + replacementIndex - beginIndex;
input = input.slice(0, replacementIndex) + replacementValue + input.slice(replacementIndex + 1);
offset += replacementValue.length - 1;
} while (++index < length);
return input;
};
/**
* Patches a given string or array of strings by apply all the applicable
* replacements done in the given range.
*
* @param {String|String[]} input - string or splitted string to apply replacements on
* @param {Number} beginIndex - index from where to apply replacements in input
* @param {Number} endIndex - index until where to apply replacements in input
* @returns {String|String[]}
*/
ReplacementTracker.prototype.apply = function (input, beginIndex, endIndex) {
var i,
ii,
length,
_endIndex,
_beginIndex,
value = input;
// apply replacements in string
if (typeof input === STRING) {
return this._applyInString(input, beginIndex, endIndex);
}
// apply replacements in the splitted string (Array)
_beginIndex = beginIndex;
// traverse all the segments until all the replacements are patched
for (i = 0, ii = input.length; i < ii; ++i) {
value = input[i];
_endIndex = _beginIndex + (length = value.length);
// apply replacements applicable for individual segment
input[i] = this._applyInString(value, _beginIndex, _endIndex);
_beginIndex += length + 1; // + 1 separator
}
return input;
};
/**
* Normalize the given string by replacing the variables which includes
* reserved characters in its name.
* The replaced characters are added to the given replacement tracker instance.
*
* @private
* @param {String} str - string to normalize
* @param {ReplacementTracker} replacements - tracker to store replacements
* @returns {String}
*/
function normalizeVariables (str, replacements) {
var normalizedString = E,
pointer = 0, // pointer till witch the string is normalized
variable,
match,
index;
// find all the instances of {{<variable>}} which includes reserved chars
// "Hello {{user#name}}!!!"
// ↑ (pointer = 0)
while ((match = REGEX_EXTRACT_VARS.exec(str)) !== null) {
// {{user#name}}
variable = match[0];
// starting index of the {{variable}} in the string
// "Hello {{user#name}}!!!"
// ↑ (index = 6)
index = match.index;
// [pointer, index) string is normalized + the safe replacement character
// "Hello " + "_"
normalizedString += str.slice(pointer, index) + SAFE_REPLACE_CHAR;
// track the replacement done for the {{variable}}
replacements.add(variable, index);
// update the pointer
// "Hello {{user#name}}!!!"
// ↑ (pointer = 19)
pointer = index + variable.length;
}
// avoid slicing the string in case of no matches
if (pointer === 0) {
return str;
}
// whatever left in the string is normalized as well
if (pointer < str.length) {
// "Hello _" + "!!!"
normalizedString += str.slice(pointer);
}
return normalizedString;
}
/**
* Update replaced characters in the URL object with its original value.
*
* @private
* @param {Object} url - url to apply replacements on
* @param {ReplacementTracker} replacements - tracked replacements
*/
function applyReplacements (url, replacements) {
var i,
ii,
prop;
// traverse each URL property in the given order
for (i = 0, ii = URL_PROPERTIES_ORDER.length; i < ii; ++i) {
prop = url[URL_PROPERTIES_ORDER[i]];
// bail out if the given property is not set (undefined or E)
if (!(prop && prop.value)) {
continue;
}
prop.value = replacements.apply(prop.value, prop.beginIndex, prop.endIndex);
}
return url;
}
/**
* Parses the input string by decomposing the URL into constituent parts,
* such as path, host, port, etc.
*
* @private
* @param {String} urlString - url string to parse
* @returns {Object}
*/
function url_parse (urlString) {
// trim leading whitespace characters
urlString = String(urlString).trimLeft();
var url = {
protocol: { value: undefined, beginIndex: 0, endIndex: 0 },
auth: { value: undefined, beginIndex: 0, endIndex: 0 },
host: { value: undefined, beginIndex: 0, endIndex: 0 },
port: { value: undefined, beginIndex: 0, endIndex: 0 },
path: { value: undefined, beginIndex: 0, endIndex: 0 },
query: { value: undefined, beginIndex: 0, endIndex: 0 },
hash: { value: undefined, beginIndex: 0, endIndex: 0 }
},
parsedUrl = {
raw: urlString,
protocol: undefined,
auth: undefined,
host: undefined,
port: undefined,
path: undefined,
query: undefined,
hash: undefined
},
replacements = new ReplacementTracker(),
pointer = 0,
length,
index,
port;
// bail out if input string is empty
if (!urlString) {
return parsedUrl;
}
// normalize the given string
urlString = normalizeVariables(urlString, replacements);
length = urlString.length;
// 1. url.hash
if ((index = urlString.indexOf(HASH)) !== -1) {
// extract from the back
url.hash.value = urlString.slice(index + 1);
url.hash.beginIndex = pointer + index + 1;
url.hash.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 2. url.query
if ((index = urlString.indexOf(QUERY_SEPARATOR)) !== -1) {
// extract from the back
url.query.value = urlString.slice(index + 1).split(AMPERSAND);
url.query.beginIndex = pointer + index + 1;
url.query.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 3. url.protocol
if ((index = urlString.indexOf(PROTOCOL_SEPARATOR)) !== -1) {
// extract from the front
url.protocol.value = urlString.slice(0, index);
url.protocol.beginIndex = pointer;
url.protocol.endIndex = pointer + index;
urlString = urlString.slice(index + 3);
length -= index + 3;
pointer += index + 3;
}
// protocol can be separated using :\\ as well
else if ((index = urlString.indexOf(PROTOCOL_SEPARATOR_WITH_BACKSLASH)) !== -1) {
// extract from the front
url.protocol.value = urlString.slice(0, index);
url.protocol.beginIndex = pointer;
url.protocol.endIndex = pointer + index;
urlString = urlString.slice(index + 3);
length -= index + 3;
pointer += index + 3;
}
// 4. url.path
urlString = urlString.replace(/\\/g, '/'); // sanitize path
if ((index = urlString.indexOf(SLASH)) !== -1) {
// extract from the back
url.path.value = urlString.slice(index + 1).split(SLASH);
url.path.beginIndex = pointer + index + 1;
url.path.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 5. url.auth
if ((index = urlString.lastIndexOf(AUTH_SEPARATOR)) !== -1) {
// extract from the front
url.auth.value = urlString.slice(0, index);
url.auth.beginIndex = pointer;
url.auth.endIndex = pointer + index;
urlString = urlString.slice(index + 1);
length -= index + 1;
pointer += index + 1;
// separate username:password
if ((index = url.auth.value.indexOf(COLON)) === -1) {
url.auth.value = [url.auth.value];
}
else {
url.auth.value = [url.auth.value.slice(0, index), url.auth.value.slice(index + 1)];
}
}
// 6. url.port
if ((index = urlString.lastIndexOf(COLON)) !== -1 &&
// eslint-disable-next-line lodash/prefer-includes
(port = urlString.slice(index + 1)).indexOf(CLOSING_SQUARE_BRACKET) === -1
) {
// extract from the back
url.port.value = port;
url.port.beginIndex = pointer + index + 1;
url.port.endIndex = pointer + length;
urlString = urlString.slice(0, (length = index));
}
// 7. url.host
if (urlString) {
url.host.value = urlString.split(DOMAIN_SEPARATOR);
url.host.beginIndex = pointer;
url.host.endIndex = pointer + length;
}
// apply replacements back, if any
replacements.count() && applyReplacements(url, replacements);
// finally, prepare parsed url
parsedUrl.protocol = url.protocol.value;
parsedUrl.auth = url.auth.value;
parsedUrl.host = url.host.value;
parsedUrl.port = url.port.value;
parsedUrl.path = url.path.value;
parsedUrl.query = url.query.value;
parsedUrl.hash = url.hash.value;
return parsedUrl;
}
/* eslint-disable object-shorthand */
module.exports = {
parse: function (url) {
if (typeof url !== STRING) {
url = '';
}
url = url_parse(url);
var pathVariables,
pathVariableKeys = {};
if (url.auth) {
url.auth = {
user: url.auth[0],
password: url.auth[1]
};
}
if (url.query) {
url.query = url.query.map(parseQueryParam);
}
// extract path variables
pathVariables = _.transform(url.path, function (res, segment) {
// check if the segment has path variable prefix followed by the variable name and
// the variable is not already added in the list.
if (_.startsWith(segment, COLON) &&
segment !== COLON &&
!pathVariableKeys[segment]) {
pathVariableKeys[segment] = true;
res.push({ key: segment.slice(1) }); // remove path variable prefix.
}
}, []);
url.variable = pathVariables.length ? pathVariables : undefined;
return url;
},
unparse: function (urlObj) {
var rawUrl = E,
path,
queryString,
authString,
firstEnabledParam = true;
if (urlObj.protocol) {
rawUrl += (_.endsWith(urlObj.protocol, PROTOCOL_SEPARATOR) ?
urlObj.protocol : urlObj.protocol + PROTOCOL_SEPARATOR);
}
if (urlObj.auth) {
if (typeof urlObj.auth.user === STRING) {
authString = urlObj.auth.user;
}
if (typeof urlObj.auth.password === STRING) {
!authString && (authString = E);
authString += COLON + urlObj.auth.password;
}
if (typeof authString === STRING) {
rawUrl += authString + AUTH_SEPARATOR;
}
}
if (urlObj.host) {
rawUrl += (_.isArray(urlObj.host) ? urlObj.host.join(DOMAIN_SEPARATOR) : urlObj.host.toString());
}
if (typeof _.get(urlObj.port, 'toString') === FUNCTION) {
rawUrl += COLON + urlObj.port.toString();
}
if (urlObj.path) {
path = (_.isArray(urlObj.path) ? urlObj.path.join(SLASH) : urlObj.path.toString());
rawUrl += (_.startsWith(path, SLASH) ? path : SLASH + path);
}
if (urlObj.query && urlObj.query.length) {
queryString = _.reduce(urlObj.query, function (accumulator, param) {
// ignore disabled params
if (!param || param.disabled) {
return accumulator;
}
// don't add '&' for the very first enabled param
if (firstEnabledParam) {
firstEnabledParam = false;
}
// add '&' before concatenating param
else {
accumulator += AMPERSAND;
}
return accumulator + unparseQueryParam(param);
}, E);
// either all the params are disabled or a single param is like { key: '' } (http://localhost?)
// in that case, query separator ? must be included in the raw URL.
if (queryString === E && firstEnabledParam) {
// unset querystring if there are no enabled params
queryString = undefined;
}
if (typeof queryString === STRING) {
rawUrl += QUERY_SEPARATOR + queryString;
}
}
if (typeof urlObj.hash === STRING) {
rawUrl += HASH + urlObj.hash;
}
return rawUrl;
}
};

464
node_modules/postman-collection-transformer/lib/util.js generated vendored Normal file
View File

@@ -0,0 +1,464 @@
/* eslint-disable object-shorthand */
var _ = require('lodash'),
url = require('./url'),
rnd = Math.random;
module.exports = {
// @todo: Add support for a `json` type once it becomes available
typeMap: {
string: 'string',
boolean: 'boolean',
number: 'number'
},
/**
* Returns unique GUID on every call as per pseudo-number RFC4122 standards.
*
* @type {function}
* @returns {string}
*/
uid: function () {
var n,
r,
E = '',
H = '-'; // r = result , n = numeric variable for positional checks
// if "n" is not 9 or 14 or 19 or 24 return a random number or 4
// if "n" is not 15 generate a random number from 0 to 15
// `(n ^ 20 ? 16 : 4)` := unless "n" is 20, in which case a random number from 8 to 11 otherwise 4
//
// in other cases (if "n" is 9,14,19,24) insert "-"
// eslint-disable-next-line curly
for (r = n = E; n++ < 36; r += n * 51 & 52 ? (n ^ 15 ? 8 ^ rnd() * (n ^ 20 ? 16 : 4) : 4).toString(16) : H);
return r;
},
urlparse: function (u) {
return url.parse(u);
},
urlunparse: function (urlObj) {
return url.unparse(urlObj);
},
/**
* A generic utility method to sanitize variable transformations across collection formats.
*
* @param {Object} entity - A generic object that could contain variable data.
* @param {?Object} options - The set of options for variable handling.
* @param {?Object} options.fallback - The fallback values to be used if no variables are present.
* @param {?Boolean} options.noDefaults - When set to true, id will be retained.
* @param {?Boolean} options.retainEmptyValues - When set to true, empty property values will be set to ''
* @param {?Boolean} options.retainIds - When set to true, ids are left as is.
* instead of being deleted.
* @param {Object} [modifiers] - A set of behavioral modifiers for variable handling.
* @param {Boolean} [modifiers.isV1=false] - When set to true, looks for the pathVariableData property as well.
* @returns {Object[]} - The set of sanitized entity level variables.
*/
handleVars: function (entity, options, modifiers) {
!options && (options = {});
var self = this,
retainIds = options.retainIds,
noDefaults = options.noDefaults,
isV1 = modifiers && modifiers.isV1,
retainEmpty = options.retainEmptyValues,
source = entity && (entity.variables || entity.variable || (isV1 && entity.pathVariableData)),
fallback = options.fallback && options.fallback.values,
result = _.map(source || fallback, function (item) {
var result = {};
// add id only when retainIds is set
if (retainIds) {
// retain id only when `id` field is present
if (_.has(item, 'id')) {
result.id = item.id;
}
// or, create a new id if noDefaults is false
else if (!noDefaults) {
result.id = self.uid();
}
}
result.key = item.key || item.id;
result.value = item.value;
item.type && (result.type = item.type === 'text' ? 'string' : item.type);
item.disabled && (result.disabled = true);
if (item.description) { result.description = item.description; }
else if (retainEmpty) { result.description = null; }
return result;
});
if (result.length) { return result; }
},
/**
* Performs auth cleansing common to all sorts of auth transformations.
*
* @param {Object} entity - The wrapped auth entity to be cleaned.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {Object|*} - The processed auth data.
*/
cleanAuth: function (entity, options) {
!options && (options = {});
var auth = entity && entity.auth;
if (auth === null) { return null; } // eslint-disable-line security/detect-possible-timing-attacks
if (!(auth && auth.type)) { return; }
if (auth.type === 'noauth') {
return options.excludeNoauth ? null : { type: 'noauth' };
}
return auth;
},
cleanEmptyValue: function (entity, property, retainEmpty) {
if (_.has(entity, property) && !entity[property]) {
retainEmpty ? (entity[property] = null) : (delete entity[property]);
}
return entity;
},
/**
* Transforms an array of auth params to their object equivalent.
*
* @param {Object} entity - The wrapper object for the array of auth params.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {*}
*/
authArrayToMap: function (entity, options) {
var type,
result,
self = this,
auth = self.cleanAuth(entity, options);
if (!auth) { return auth; }
result = { type: (type = auth.type) };
if (type !== 'noauth') {
result[type] = _.transform(auth[type], function (result, param) {
result[param.key] = param.value;
}, {});
}
return result;
},
/**
* Transforms an object of auth params to their array equivalent.
*
* @param {Object} entity - The wrapper object for the array of auth params.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {*}
*/
authMapToArray: function (entity, options) {
var type,
params,
result,
self = this,
auth = self.cleanAuth(entity, options);
if (!auth) { return auth; }
result = { type: (type = auth.type) };
if (type !== 'noauth') {
// @todo: Handle all non _ prefixed properties, ala request bodies
params = _.map(auth[type], function (value, key) {
return {
key: key,
value: value,
type: self.typeMap[typeof value] || 'any'
};
});
params.length && (result[type] = params);
}
return result;
},
/**
* Sanitizes a collection SDK compliant auth list.
*
* @param {Object} entity - The wrapper entity for the auth manifest.
* @param {?Object} options - The set of options for the current auth cleansing operation.
* @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
* @returns {Object[]} - An array of raw collection SDK compliant auth parameters.
*/
sanitizeAuthArray: function (entity, options) {
var type,
result,
self = this,
auth = self.cleanAuth(entity, options);
if (!auth) { return auth; }
result = { type: (type = auth.type) };
if (type !== 'noauth') {
result[type] = _.map(auth[type], function (param) {
return {
key: param.key,
value: param.value,
type: (param.type === 'text' ? 'string' : param.type) || self.typeMap[typeof param.value] || 'any'
};
});
}
return result;
},
/**
* A helper function to determine if the provided v1 entity has legacy properties.
*
* @private
* @param {Object} entityV1 - The v1 entity to be checked for the presence of legacy properties.
* @param {String} type - The type of property to be adjudged against.
* @returns {Boolean|*} - A flag to indicate the legacy property status of the passed v1 entity.
*/
notLegacy: function (entityV1, type) {
if (!entityV1) { return; }
switch (type) {
case 'event':
return !(entityV1.tests || entityV1.preRequestScript);
case 'auth':
return _.has(entityV1, 'auth') && !(_.has(entityV1, 'currentHelper') || entityV1.helperAttributes);
default:
return true;
}
},
authMappersFromLegacy: {
apikeyAuth: function (oldParams) {
return oldParams && {
key: oldParams.key,
value: oldParams.value,
in: oldParams.in
};
},
basicAuth: function (oldParams) {
return oldParams && {
username: oldParams.username,
password: oldParams.password,
saveHelperData: oldParams.saveToRequest,
showPassword: false
};
},
bearerAuth: function (oldParams) {
return oldParams && {
token: oldParams.token
};
},
digestAuth: function (oldParams) {
return oldParams && {
algorithm: oldParams.algorithm,
username: oldParams.username,
realm: oldParams.realm,
password: oldParams.password,
nonce: oldParams.nonce,
nonceCount: oldParams.nonceCount,
clientNonce: oldParams.clientNonce,
opaque: oldParams.opaque,
qop: oldParams.qop,
disableRetryRequest: oldParams.disableRetryRequest
};
},
oAuth1: function (oldParams) {
return oldParams && {
consumerKey: oldParams.consumerKey,
consumerSecret: oldParams.consumerSecret,
token: oldParams.token,
tokenSecret: oldParams.tokenSecret,
signatureMethod: oldParams.signatureMethod,
timestamp: oldParams.timestamp,
nonce: oldParams.nonce,
version: oldParams.version,
realm: oldParams.realm,
addParamsToHeader: oldParams.header,
autoAddParam: oldParams.auto,
addEmptyParamsToSign: oldParams.includeEmpty
};
},
hawkAuth: function (oldParams) {
return oldParams && {
authId: oldParams.hawk_id,
authKey: oldParams.hawk_key,
algorithm: oldParams.algorithm,
user: oldParams.user,
saveHelperData: oldParams.saveToRequest,
nonce: oldParams.nonce,
extraData: oldParams.ext,
appId: oldParams.app,
delegation: oldParams.dlg,
timestamp: oldParams.timestamp
};
},
ntlmAuth: function (oldParams) {
return oldParams && {
username: oldParams.username,
password: oldParams.password,
domain: oldParams.domain,
workstation: oldParams.workstation,
disableRetryRequest: oldParams.disableRetryRequest
};
},
oAuth2: function (oldParams) {
return oldParams && {
accessToken: oldParams.accessToken,
addTokenTo: oldParams.addTokenTo,
callBackUrl: oldParams.callBackUrl,
authUrl: oldParams.authUrl,
accessTokenUrl: oldParams.accessTokenUrl,
clientId: oldParams.clientId,
clientSecret: oldParams.clientSecret,
clientAuth: oldParams.clientAuth,
grantType: oldParams.grantType,
scope: oldParams.scope,
username: oldParams.username,
password: oldParams.password,
tokenType: oldParams.tokenType,
redirectUri: oldParams.redirectUri,
refreshToken: oldParams.refreshToken
};
},
// Only exists for consistency
awsSigV4: function (oldParams) {
return oldParams;
}
},
authMappersFromCurrent: {
apikeyAuth: function (newParams) {
return newParams && {
id: 'apikey',
key: newParams.key,
value: newParams.value,
in: newParams.in
};
},
basicAuth: function (newParams) {
return newParams && {
id: 'basic',
username: newParams.username,
password: newParams.password,
saveToRequest: newParams.saveHelperData
};
},
bearerAuth: function (newParams) {
return newParams && {
id: 'bearer',
token: newParams.token
};
},
digestAuth: function (newParams) {
return newParams && {
id: 'digest',
algorithm: newParams.algorithm,
username: newParams.username,
realm: newParams.realm,
password: newParams.password,
nonce: newParams.nonce,
nonceCount: newParams.nonceCount,
clientNonce: newParams.clientNonce,
opaque: newParams.opaque,
qop: newParams.qop,
disableRetryRequest: newParams.disableRetryRequest
};
},
oAuth1: function (newParams) {
return newParams && {
id: 'oAuth1',
consumerKey: newParams.consumerKey,
consumerSecret: newParams.consumerSecret,
token: newParams.token,
tokenSecret: newParams.tokenSecret,
signatureMethod: newParams.signatureMethod,
timestamp: newParams.timeStamp || newParams.timestamp,
nonce: newParams.nonce,
version: newParams.version,
realm: newParams.realm,
header: newParams.addParamsToHeader,
auto: newParams.autoAddParam,
includeEmpty: newParams.addEmptyParamsToSign
};
},
hawkAuth: function (newParams) {
return newParams && {
id: 'hawk',
hawk_id: newParams.authId,
hawk_key: newParams.authKey,
algorithm: newParams.algorithm,
user: newParams.user,
saveToRequest: newParams.saveHelperData,
nonce: newParams.nonce,
ext: newParams.extraData,
app: newParams.appId,
dlg: newParams.delegation,
timestamp: newParams.timestamp
};
},
ntlmAuth: function (newParams) {
return newParams && {
id: 'ntlm',
username: newParams.username,
password: newParams.password,
domain: newParams.domain,
workstation: newParams.workstation,
disableRetryRequest: newParams.disableRetryRequest
};
},
oAuth2: function (newParams) {
return newParams && {
id: 'oAuth2',
accessToken: newParams.accessToken,
addTokenTo: newParams.addTokenTo,
callBackUrl: newParams.callBackUrl,
authUrl: newParams.authUrl,
accessTokenUrl: newParams.accessTokenUrl,
clientId: newParams.clientId,
clientSecret: newParams.clientSecret,
clientAuth: newParams.clientAuth,
grantType: newParams.grantType,
scope: newParams.scope,
username: newParams.username,
password: newParams.password,
tokenType: newParams.tokenType,
redirectUri: newParams.redirectUri,
refreshToken: newParams.refreshToken
};
},
// Only exists for consistency
awsSigV4: function (newParams) {
return newParams;
}
},
/**
* Validate protocolProfileBehavior property's value.
*
* @param {Object} source - A generic object that could contain the protocolProfileBehavior property.
* @param {?Object} destination - The destination object that needs the addition of protocolProfileBehavior.
* @returns {Boolean} - A Boolean value to decide whether to include the property or not.
*/
addProtocolProfileBehavior: function (source, destination) {
var behavior = source && source.protocolProfileBehavior;
// make sure it's a non-empty plain object
if (!(_.isPlainObject(behavior) && !_.isEmpty(behavior))) { return false; }
destination && (destination.protocolProfileBehavior = behavior);
return true;
}
};