886 lines
32 KiB
JavaScript
886 lines
32 KiB
JavaScript
/* 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;
|
|
}
|
|
};
|