-
Notifications
You must be signed in to change notification settings - Fork 227
Description
Hello!
We would like the library to be able to process multiple examples given in the parameters section. Because we have seen that there are some places in the code that check if there is an 'example' or if there is an array of 'examples' then it gets just the first one. Our idea would be that each one of this examples create a new 'response' in the resulting collection.
This way if we have a parameter 'foo' with examples 1 and 2 and one example of response with 200 OK code response, then in the obtained collection we would see three different examples.
We have implemented some code affecting the schemaUtils.js with just two changes:
1- Creation of this new function
` /**
- Builds an original request object for response examples
- @param {Object} params - Parameters for building the request
- @param {Object} params.item - The Postman request item
- @param {Object} params.reqParams - Request parameters (query, header, path)
- @param {Object} params.operationItem - Operation item from OpenAPI spec
- @param {Object} params.components - Components for resolving references
- @param {Object} params.options - Global options
- @param {Object} params.schemaCache - Schema cache object
- @param {Array} params.authQueryParams - Auth query parameters to add
- @param {Array} params.authHeaders - Auth headers to add
- @param {Object} params.parameterOverrides - Optional overrides for specific parameters
- @returns {Object} Original request object for response
*/
buildOriginalRequestForResponse: function(params) {
const {
item,
reqParams,
operationItem,
components,
options,
schemaCache,
authQueryParams,
authHeaders,
parameterOverrides
} = params;
let originalRequest = {},
originalRequestHeaders = [],
originalRequestQueryParams = [],
exampleRequestBody;
// Build query params
if (reqParams.query && reqParams.query.length > 0) {
reqParams.query.forEach((queryParam) => {
// Check if there's an override for this parameter
if (parameterOverrides && parameterOverrides[queryParam.name]) {
let exampleValue = parameterOverrides[queryParam.name];
if (exampleValue !== undefined) {
originalRequestQueryParams.push(queryParam.name + '=' + String(exampleValue));
}
else {
// If override value is undefined, generate from schema
let convertedParams = this.convertToPmQueryParameters(queryParam, REQUEST_TYPE.EXAMPLE,
components, options, schemaCache);
convertedParams.forEach((pmParam) => {
originalRequestQueryParams.push(pmParam.key + '=' + pmParam.value);
});
}
}
else {
// Use standard conversion for parameters without overrides
let convertedParams = this.convertToPmQueryParameters(queryParam, REQUEST_TYPE.EXAMPLE,
components, options, schemaCache);
convertedParams.forEach((pmParam) => {
originalRequestQueryParams.push(pmParam.key + '=' + pmParam.value);
});
}
}, this);
}
// Add auth query params if present
if (authQueryParams && authQueryParams.length > 0) {
originalRequestQueryParams = _.concat(originalRequestQueryParams, authQueryParams);
}
// Build URL with path variables
let clonedItemURL = _.cloneDeep(item.request.url),
pathVarMembers = [];
clonedItemURL.variables.members.forEach((pathVar) => {
let updatedPathVar = _.cloneDeep(pathVar);
// Check if there's an override for this path variable
if (parameterOverrides && parameterOverrides[pathVar.key] !== undefined) {
updatedPathVar.value = String(parameterOverrides[pathVar.key]);
}
pathVarMembers.push(updatedPathVar);
}, this);
originalRequest.method = item.request.method;
originalRequest.url = clonedItemURL;
originalRequest.url.variable = pathVarMembers;
originalRequest.url.query = [];
if (originalRequestQueryParams.length) {
originalRequest.url.query = originalRequestQueryParams.join('&');
}
// Build headers
_.forEach(reqParams.header, (header) => {
// Check if there's an override for this header
if (parameterOverrides && parameterOverrides[header.name] !== undefined) {
let headerWithOverride = _.cloneDeep(header);
let overrideValue = parameterOverrides[header.name];
headerWithOverride.example = overrideValue;
_.set(headerWithOverride, 'schema.example', overrideValue);
delete headerWithOverride.examples;
originalRequestHeaders.push(this.convertToPmHeader(headerWithOverride, REQUEST_TYPE.EXAMPLE,
PARAMETER_SOURCE.REQUEST, components, options, schemaCache));
}
else {
originalRequestHeaders.push(this.convertToPmHeader(header, REQUEST_TYPE.EXAMPLE,
PARAMETER_SOURCE.REQUEST, components, options, schemaCache));
}
}, this);
// Add auth headers if present
if (authHeaders && authHeaders.length > 0) {
originalRequestHeaders = _.concat(originalRequestHeaders, authHeaders);
}
originalRequest.header = originalRequestHeaders;
// Set request body
try {
exampleRequestBody = this.convertToPmBody(operationItem.properties.requestBody,
REQUEST_TYPE.EXAMPLE, components, options, schemaCache);
originalRequest.body = exampleRequestBody.body ? exampleRequestBody.body.toJSON() : {};
}
catch (e) {
originalRequest.body = {};
}
return originalRequest;
},`
2-Modification of the convertRequestToItem function:
` /**
-
function to convert an openapi path item to postman item
-
@param {*} openapi openapi object with root properties
-
@param {*} operationItem path operationItem from tree structure
-
@param {object} components - components defined in the OAS spec. These are used to
-
resolve references while generating params.
-
@param {object} options - a standard list of options that's globally passed around. Check options.js for more.
-
@param {object} schemaCache - object storing schemaFaker and schmeResolution caches
-
@param {array} variableStore - array
-
@param {boolean} fromWebhooks - true if we are processing the webhooks group, false by default
-
@returns {Object} postman request Item
-
@no-unit-test
*/
convertRequestToItem: function(openapi, operationItem, components,
options, schemaCache, variableStore, fromWebhooks = false) {
var reqName,
pathVariables = openapi.baseUrlVariables,
operation = operationItem.properties,
reqBody = operationItem.properties.requestBody,
itemParams = operationItem.properties.parameters,
reqParams = this.getParametersForPathItem(itemParams, options),
baseUrl = fromWebhooks ?
{{${this.cleanWebhookName(operationItem.path)}}}:
'{{baseUrl}}',
pathVarArray = [],
authHelper,
item,
serverObj,
displayUrl,
reqUrl = fromWebhooks ? '' : '/' + operationItem.path,
pmBody,
authMeta,
swagResponse,
localServers = fromWebhooks ? '' : _.get(operationItem, 'properties.servers'),
sanitizeResult,
globalServers = fromWebhooks ? '' : _.get(operationItem, 'servers'),
responseMediaTypes,
acceptHeader;// handling path templating in request url if any
// convert all {anything} to {{anything}}
reqUrl = this.fixPathVariablesInUrl(reqUrl);// convert all /{{one}}/{{two}} to /:one/:two
// Doesn't touch /{{file}}.{{format}}
sanitizeResult = this.sanitizeUrlPathParams(reqUrl, reqParams.path);// Updated reqUrl
reqUrl = sanitizeResult.url;// Updated reqParams.path
reqParams.path = sanitizeResult.pathVars;// Add collection variables to the variableStore.
sanitizeResult.collectionVars.forEach((element) => {
if (!variableStore[element.name]) {
let fakedData = options.schemaFaker ?
safeSchemaFaker(element.schema || {}, options.requestParametersResolution, PROCESSING_TYPE.CONVERSION,
PARAMETER_SOURCE.REQUEST, components, SCHEMA_FORMATS.DEFAULT, schemaCache, options) : '',
convertedPathVar = _.get(this.convertParamsWithStyle(element, fakedData, PARAMETER_SOURCE.REQUEST,
components, schemaCache, options), '[0]', {});variableStore[element.name] = _.assign(convertedPathVar, { key: element.name, type: 'collection' });}
});
// accounting for the overriding of the root level and path level servers object if present at the operation levelif (Array.isArray(localServers) && localServers.length) {
serverObj = operationItem.properties.servers[0];// convert all {anything} to {{anything}}
baseUrl = this.fixPathVariablesInUrl(serverObj.url);// add serverObj variables to pathVarArray
if (serverObj.variables) {
_.forOwn(serverObj.variables, (value, key) => {
pathVarArray.push({
name: key,
value: value.default || '',
description: this.getParameterDescription(value)
});
});// use pathVarArray to sanitize url for path params and collection variables. sanitizeResult = this.sanitizeUrlPathParams(baseUrl, pathVarArray); // update the base url with update url baseUrl = sanitizeResult.url; // Add new collection variables to the variableStore sanitizeResult.collectionVars.forEach((element) => { if (!variableStore[element.name]) { variableStore[element.name] = { key: element.name, value: element.value || '', description: element.description, type: 'collection' }; } }); // remove all the collection variables from serverObj.variables serverObj.pathVariables = {}; sanitizeResult.pathVars.forEach((element) => { serverObj.pathVariables[element.name] = serverObj.variables[element.name]; }); // use this new filtered serverObj.pathVariables // to generate pm path variables. pathVarArray = this.convertPathVariables('method', [], serverObj.pathVariables, components, options, schemaCache);}
baseUrl += reqUrl;
}
else {
// accounting for the overriding of the root level servers object if present at the path level
if (Array.isArray(globalServers) && globalServers.length) {
// All the global servers present at the path level are taken care of in generateTrieFromPaths
// Just adding the same structure of the url as the display URL.
displayUrl = '{{' + this.fixPathVariableName(operationItem.path) + '}}' + reqUrl;
}
// In case there are no server available use the baseUrl
else {
baseUrl += reqUrl;
if (pathVariables || fromWebhooks) {
displayUrl = baseUrl;
}
else {
displayUrl = '{{baseUrl}}' + reqUrl;
}
}
pathVarArray = this.convertPathVariables('root', [], pathVariables, components, options, schemaCache);
}switch (options.requestNameSource) {
case 'fallback' : {
// operationId is usually camelcase or snake case
if (fromWebhooks) {
reqName = operation.summary ||
utils.insertSpacesInName(operation.operationId) ||
operation.description ||
${this.cleanWebhookName(operationItem.path)} - ${operationItem.method};
}
else {
reqName = operation.summary ||
utils.insertSpacesInName(operation.operationId) ||
operation.description || reqUrl;
}
break;
}
case 'url' : {
reqName = displayUrl || baseUrl;
break;
}
default : {
reqName = operation[options.requestNameSource];
break;
}
}
if (!reqName) {
throw new openApiErr(requestNameSource (${options.requestNameSource})+
in options is invalid or property does not exist in ${operationItem.path});
}// handling authentication here (for http type only)
if (!options.alwaysInheritAuthentication) {
authHelper = this.getAuthHelper(openapi, operation.security);
}// creating the request object
item = new Item({
name: reqName,
request: {
description: operation.description,
url: displayUrl || baseUrl,
name: reqName,
method: operationItem.method.toUpperCase()
}
});// using the auth helper
authMeta = operation['x-postman-meta'];
if (authMeta && authMeta.currentHelper && authMap[authMeta.currentHelper]) {
let thisAuthObject = {
type: authMap[authMeta.currentHelper]
};thisAuthObject[authMap[authMeta.currentHelper]] = authMeta.helperAttributes;
item.request.auth = new RequestAuth(thisAuthObject);
}
else {
item.request.auth = authHelper;
}// adding query params to postman request url.
_.forEach(reqParams.query, (queryParam) => {
this.convertToPmQueryParameters(queryParam, REQUEST_TYPE.ROOT, components, options, schemaCache)
.forEach((pmParam) => {
item.request.url.addQueryParams(pmParam);
});
});
item.request.url.query.members.forEach((query) => {
// Collection v2.1 schema allows query param value to be string/null
if (typeof query.value !== 'string') {
try {
// convert other datatype to string (i.e. number, boolean etc)
query.value = JSON.stringify(query.value);
}
catch (e) {
// JSON.stringify can fail in few cases, suggest invalid type for such case
// eslint-disable-next-line max-len
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Exceptions
query.value = 'INVALID_QUERY_PARAM_TYPE';
}
}
});
item.request.url.variables.clear();
item.request.url.variables.assimilate(this.convertPathVariables('param', pathVarArray, reqParams.path,
components, options, schemaCache));// Making sure description never goes out as an object
// App / Collection transformer fail with the object syntax
if (item.request.url.variables.members && item.request.url.variables.members.length > 0) {
item.request.url.variables.members = _.map(item.request.url.variables.members, (m) => {
if (m.description && typeof m.description === 'object' && m.description.content) {
m.description = m.description.content;
}
return m;
});
}// adding headers to request from reqParam
.forEach(reqParams.header, (header) => {
if (options.keepImplicitHeaders || !.includes(IMPLICIT_HEADERS, .toLower(.get(header, 'name')))) {
item.request.addHeader(this.convertToPmHeader(header, REQUEST_TYPE.ROOT, PARAMETER_SOURCE.REQUEST,
components, options, schemaCache));
}
});// adding Request Body and Content-Type header
if (reqBody) {
if (reqBody.$ref) {
reqBody = this.getRefObject(reqBody.$ref, components, options);
}
pmBody = this.convertToPmBody(reqBody, REQUEST_TYPE.ROOT, components, options, schemaCache);
item.request.body = pmBody.body;if (!options.keepImplicitHeaders || (!_.find(reqParams.header, (h) => {
return .toLower(.get(h, 'name')) === 'content-type';
}))) {
// Add detected content-type header
item.request.addHeader(pmBody.contentHeader);
}
// extra form headers if encoding is present in request Body.
// TODO: Show warning for incorrect schema if !pmBody.formHeaders
pmBody.formHeaders && pmBody.formHeaders.forEach((element) => {
item.request.addHeader(element);
});
}// Generate combinations of parameter examples (independent of responses)
let allParams = _.concat(
reqParams.query || [],
reqParams.path || [],
reqParams.header || []
);
let exampleCombinations = this.generateParameterExampleCombinations(allParams);// adding responses to request item
if (operation.responses) {
let responseAuthHelper,
authQueryParams = [],
authHeaders = [];if (options.includeAuthInfoInExample) {
responseAuthHelper = this.getResponseAuthHelper(authHelper);// override auth helper with global security definition if no operation security definition found if (_.isEmpty(authHelper)) { responseAuthHelper = this.getResponseAuthHelper(this.getAuthHelper(openapi, openapi.security)); } authQueryParams = _.map(responseAuthHelper.query, (queryParam) => { // key-value pair will be added as transformed query string return queryParam.key + '=' + queryParam.value; }); authHeaders = responseAuthHelper.header || [];}
_.forOwn(operation.responses, (response, code) => {
let convertedResponse;swagResponse = response; if (_.get(response, '$ref')) { swagResponse = this.getRefObject(response.$ref, components, options); } // Build the original request using the helper function let originalRequest = this.buildOriginalRequestForResponse({ item, reqParams, operationItem, components, options, schemaCache, authQueryParams, authHeaders, parameterOverrides: null // No overrides for standard responses }); // set accept header value as first found response content's media type if (_.isEmpty(acceptHeader)) { responseMediaTypes = _.keys(_.get(swagResponse, 'content')); if (responseMediaTypes.length > 0) { acceptHeader = { key: 'Accept', value: responseMediaTypes[0] }; } } convertedResponse = this.convertToPmResponse(swagResponse, code, originalRequest, components, options, schemaCache); convertedResponse && item.responses.add(convertedResponse);});
}// Generate responses for each parameter example combination (independent of response codes)
if (exampleCombinations && exampleCombinations.length > 0) {
let templateResponse,
templateResponseCode,
responseAuthHelper,
authQueryParams = [],
authHeaders = [];// Get the first available response as template, or create a default one
if (operation.responses && Object.keys(operation.responses).length > 0) {
templateResponseCode = Object.keys(operation.responses)[0];
templateResponse = operation.responses[templateResponseCode];
if (_.get(templateResponse, '$ref')) {
templateResponse = this.getRefObject(templateResponse.$ref, components, options);
}
}
else {
// Create a default response if no responses are defined
templateResponseCode = '200';
templateResponse = {
description: 'Successful response'
};
}if (options.includeAuthInfoInExample) {
responseAuthHelper = this.getResponseAuthHelper(authHelper);// override auth helper with global security definition if no operation security definition found if (_.isEmpty(authHelper)) { responseAuthHelper = this.getResponseAuthHelper(this.getAuthHelper(openapi, openapi.security)); } authQueryParams = _.map(responseAuthHelper.query, (queryParam) => { // key-value pair will be added as transformed query string return queryParam.key + '=' + queryParam.value; }); authHeaders = responseAuthHelper.header || [];}
exampleCombinations.forEach((combination) => {
let exampleConvertedResponse,
parameterOverrides = {};// Build parameter overrides from combination values _.forEach(combination.values, (paramInfo, paramName) => { parameterOverrides[paramName] = _.get(paramInfo, 'exampleValue.value'); }); // Build the original request using the helper function let originalRequest = this.buildOriginalRequestForResponse({ item, reqParams, operationItem, components, options, schemaCache, authQueryParams, authHeaders, parameterOverrides }); exampleConvertedResponse = this.convertToPmResponse(templateResponse, templateResponseCode, originalRequest, components, options, schemaCache); if (exampleConvertedResponse) { // Update the response name to include the example combination exampleConvertedResponse.name = combination.name; item.responses.add(exampleConvertedResponse); }}, this);
}// add accept header if found and not present already
if (!_.isEmpty(acceptHeader) && !item.request.headers.has('accept')) {
item.request.addHeader(acceptHeader);
}/**
- Following is added to make sure body pruning for request methods like GET, HEAD etc is disabled'.
- https://github.com/postmanlabs/postman-runtime/blob/develop/docs/protocol-profile-behavior.md
*/
item.protocolProfileBehavior = { disableBodyPruning: true };
return item;
},`
Here you have a full example of a JSON with examples and responses that is how the OpenApi should look like:
And the JSON collection obtained and expected: