Skip to content

Commit

Permalink
Merge pull request #803 from thim81/develop-801-request-contenttype-body
Browse files Browse the repository at this point in the history
Conversion - Added option to set preferred request body content-type
  • Loading branch information
VShingala authored Jul 16, 2024
2 parents 930197a + b883271 commit fec6268
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 21 deletions.
6 changes: 6 additions & 0 deletions OPTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ collapseFolders|boolean|-|true|Importing will collapse all folders that have onl
optimizeConversion|boolean|-|true|Optimizes conversion for large specification, disabling this option might affect the performance of conversion.|CONVERSION|v1
requestParametersResolution|enum|Example, Schema|Schema|Select whether to generate the request parameters based on the [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject) in the schema.|CONVERSION|v1
exampleParametersResolution|enum|Example, Schema|Example|Select whether to generate the response parameters based on the [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject) in the schema.|CONVERSION|v1
disabledParametersValidation|boolean|-|true|Whether disabled parameters of collection should be validated|VALIDATION|v2, v1
parametersResolution|enum|Example, Schema|Schema|Select whether to generate the request and response parameters based on the [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject) in the schema.|CONVERSION|v2, v1
folderStrategy|enum|Paths, Tags|Paths|Select whether to create folders according to the spec’s paths or tags.|CONVERSION|v2, v1
schemaFaker|boolean|-|true|Whether or not schemas should be faked.|CONVERSION|v2, v1
stackLimit|integer|-|10|Number of nesting limit till which schema resolution will happen. Increasing this limit may result in more time to convert collection depending on complexity of specification. (To make sure this option works correctly "optimizeConversion" option needs to be disabled)|CONVERSION|v2, v1
includeAuthInfoInExample|boolean|-|true|Select whether to include authentication parameters in the example request.|CONVERSION|v2, v1
shortValidationErrors|boolean|-|false|Whether detailed error messages are required for request <> schema validation operations.|VALIDATION|v2, v1
validationPropertiesToIgnore|array|-|[]|Specific properties (parts of a request/response pair) to ignore during validation. Must be sent as an array of strings. Valid inputs in the array: PATHVARIABLE, QUERYPARAM, HEADER, BODY, RESPONSE_HEADER, RESPONSE_BODY|VALIDATION|v2, v1
Expand All @@ -20,5 +23,8 @@ strictRequestMatching|boolean|-|false|Whether requests should be strictly matche
allowUrlPathVarMatching|boolean|-|false|Whether to allow matching path variables that are available as part of URL itself in the collection request|VALIDATION|v2, v1
enableOptionalParameters|boolean|-|true|Optional parameters aren't selected in the collection. Once enabled they will be selected in the collection and request as well.|CONVERSION|v2, v1
keepImplicitHeaders|boolean|-|false|Whether to keep implicit headers from the OpenAPI specification, which are removed by default.|CONVERSION|v2, v1
includeWebhooks|boolean|-|false|Select whether to include Webhooks in the generated collection|CONVERSION|v2, v1
includeReferenceMap|boolean|-|false|Whether or not to include reference map or not as part of output|BUNDLE|v2, v1
includeDeprecated|boolean|-|true|Select whether to include deprecated operations, parameters, and properties in generated collection or not|CONVERSION, VALIDATION|v2, v1
alwaysInheritAuthentication|boolean|-|false|Whether authentication details should be included on every request, or always inherited from the collection.|CONVERSION|v2, v1
preferredRequestBodyType|enum|x-www-form-urlencoded, form-data, raw, first-listed|first-listed|When there are multiple content-types defined in the request body of OpenAPI, the conversion selects the preferred option content-type as request body.If "first-listed" is set, the first content-type defined in the OpenAPI spec will be selected.|CONVERSION|v2
14 changes: 14 additions & 0 deletions lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,20 @@ module.exports = {
usage: ['CONVERSION'],
supportedIn: [VERSION20, VERSION30, VERSION31],
supportedModuleVersion: [MODULE_VERSION.V2, MODULE_VERSION.V1]
},
{
name: 'Select request body type',
id: 'preferredRequestBodyType',
type: 'enum',
default: 'first-listed',
availableOptions: ['x-www-form-urlencoded', 'form-data', 'raw', 'first-listed'],
description: 'When there are multiple content-types defined in the request body of OpenAPI, the conversion ' +
'selects the preferred option content-type as request body.If "first-listed" is set, the first ' +
'content-type defined in the OpenAPI spec will be selected.',
external: false,
usage: ['CONVERSION'],
supportedIn: [VERSION20, VERSION30, VERSION31],
supportedModuleVersion: [MODULE_VERSION.V2]
}
];

Expand Down
42 changes: 36 additions & 6 deletions libV2/schemaUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,13 @@ let QUERYPARAM = 'query',

resolveRequestBodyForPostmanRequest = (context, operationItem) => {
let requestBody = operationItem.requestBody,
requestContent;
requestContent,
encodedRequestBody,
formDataRequestBody,
rawModeRequestBody;

const { preferredRequestBodyType: optionRequestBodyType } = context.computedOptions,
preferredRequestBodyType = optionRequestBodyType || 'first-listed';

if (!requestBody) {
return requestBody;
Expand All @@ -1659,15 +1665,38 @@ let QUERYPARAM = 'query',
};
}

if (requestContent[URLENCODED]) {
return resolveUrlEncodedRequestBodyForPostmanRequest(context, requestContent[URLENCODED]);
for (const contentType in requestContent) {
if (contentType === URLENCODED) {
encodedRequestBody = resolveUrlEncodedRequestBodyForPostmanRequest(context, requestContent[contentType]);
if (preferredRequestBodyType === 'first-listed') {
return encodedRequestBody;
}
}
else if (contentType === FORM_DATA) {
formDataRequestBody = resolveFormDataRequestBodyForPostmanRequest(context, requestContent[contentType]);
if (preferredRequestBodyType === 'first-listed') {
return formDataRequestBody;
}
}
else {
rawModeRequestBody = resolveRawModeRequestBodyForPostmanRequest(context, requestContent);
if (preferredRequestBodyType === 'first-listed') {
return rawModeRequestBody;
}
}
}

if (requestContent[FORM_DATA]) {
return resolveFormDataRequestBodyForPostmanRequest(context, requestContent[FORM_DATA]);
// Check if preferredRequestBodyType is provided and return the corresponding request body if available
if (preferredRequestBodyType) {
if (preferredRequestBodyType === 'x-www-form-urlencoded' && encodedRequestBody) {
return encodedRequestBody;
}
else if (preferredRequestBodyType === 'form-data' && formDataRequestBody) {
return formDataRequestBody;
}
}

return resolveRawModeRequestBodyForPostmanRequest(context, requestContent);
return rawModeRequestBody;
},

resolvePathItemParams = (context, operationParam, pathParam) => {
Expand Down Expand Up @@ -2236,6 +2265,7 @@ module.exports = {
},

resolveResponseForPostmanRequest,
resolveRequestBodyForPostmanRequest,
resolveRefFromSchema,
resolveSchema
};
41 changes: 26 additions & 15 deletions test/system/structure.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,36 @@ const optionIds = [
'includeDeprecated',
'parametersResolution',
'disabledParametersValidation',
'alwaysInheritAuthentication'
'alwaysInheritAuthentication',
'preferredRequestBodyType'
],
expectedOptions = {
collapseFolders: {
name: 'Collapse redundant folders',
type: 'boolean',
default: true,
description: 'Importing will collapse all folders that have only one child element and lack ' +
'persistent folder-level data.'
'persistent folder-level data.'
},
requestParametersResolution: {
name: 'Request parameter generation',
type: 'enum',
default: 'Schema',
availableOptions: ['Example', 'Schema'],
description: 'Select whether to generate the request parameters based on the' +
' [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the' +
' [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)' +
' in the schema.'
' [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the' +
' [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)' +
' in the schema.'
},
exampleParametersResolution: {
name: 'Response parameter generation',
type: 'enum',
default: 'Example',
availableOptions: ['Example', 'Schema'],
description: 'Select whether to generate the response parameters based on the' +
' [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the' +
' [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)' +
' in the schema.'
' [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the' +
' [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)' +
' in the schema.'
},
folderStrategy: {
name: 'Folder organization',
Expand Down Expand Up @@ -88,8 +89,8 @@ const optionIds = [
default: 'Fallback',
availableOptions: ['Url', 'Fallback'],
description: 'Determines how the requests inside the generated collection will be named.' +
' If “Fallback” is selected, the request will be named after one of the following schema' +
' values: `summary`, `operationId`, `description`, `url`.'
' If “Fallback” is selected, the request will be named after one of the following schema' +
' values: `summary`, `operationId`, `description`, `url`.'
},
schemaFaker: {
name: 'Enable Schema Faking',
Expand Down Expand Up @@ -210,9 +211,9 @@ const optionIds = [
default: 'Schema',
availableOptions: ['Example', 'Schema'],
description: 'Select whether to generate the request and response parameters based on the' +
' [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the' +
' [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)' +
' in the schema.',
' [schema](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject) or the' +
' [example](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)' +
' in the schema.',
external: true,
usage: ['CONVERSION']
},
Expand All @@ -232,6 +233,16 @@ const optionIds = [
'the collection.',
external: true,
usage: ['CONVERSION']
}, preferredRequestBodyType: {
name: 'Select request body type',
type: 'enum',
default: 'first-listed',
availableOptions: ['x-www-form-urlencoded', 'form-data', 'raw', 'first-listed'],
description: 'When there are multiple content-types defined in the request body of OpenAPI, the conversion ' +
'selects the preferred option content-type as request body.If "first-listed" is set, the first ' +
'content-type defined in the OpenAPI spec will be selected.',
external: false,
usage: ['CONVERSION']
}
};

Expand Down Expand Up @@ -312,8 +323,8 @@ describe('getOptions', function() {
describe('OPTIONS.md', function() {
it('must contain all details of options', function () {
const optionsDoc = fs.readFileSync('OPTIONS.md', 'utf-8'),
v1Options = getOptions(undefined, { external: true, moduleVersion: 'v1' }),
v2Options = getOptions(undefined, { external: true, moduleVersion: 'v2' }),
v1Options = getOptions(undefined, { moduleVersion: 'v1' }),
v2Options = getOptions(undefined, { moduleVersion: 'v2' }),
allOptions = _.uniqBy(_.concat(v1Options, v2Options), 'id');

expect(optionsDoc).to.eql(generateOptionsDoc(allOptions));
Expand Down
184 changes: 184 additions & 0 deletions test/unit/schemaUtilsV2.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const {
resolveRequestBodyForPostmanRequest
} = require('../../libV2/schemaUtils.js'),
concreteUtils = require('../../lib/30XUtils/schemaUtils30X'),
expect = require('chai').expect,

// Example operationItem
operationItem = {
put: {
'tags': [
'Administration: Users'
],
'summary': 'Create or Update User',
'operationId': 'User',
'requestBody': {
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'foo': {
'type': 'string'
}
}
}
},
'text/json': {
'schema': {
'type': 'object',
'properties': {
'foo': {
'type': 'string'
}
}
}
},
'application/xml': {
'schema': {
'type': 'object',
'properties': {
'foo': {
'type': 'string'
}
}
}
},
'multipart/form-data': {
'schema': {
'type': 'object',
'properties': {
'foo': {
'type': 'string'
}
}
}
},
'application/x-www-form-urlencoded': {
'schema': {
'type': 'object',
'properties': {
'foo': {
'type': 'string'
}
}
}
}
},
'description': 'The User request.',
'required': true
},
'responses': {
'200': {
'description': 'OK',
'content': {
'application/json': {
'schema': {
'type': 'object',
'properties': {}
}
}
}
}
}
}
};

describe('resolveRequestBodyForPostmanRequest function', function () {

it('should return first-listed request body when preferredRequestBodyType is not set', function () {
const contextTest = {
concreteUtils,
schemaCache: {},
schemaFakerCache: {},
computedOptions: {
parametersResolution: 'schema'
}
},
operationItemTest = operationItem.put,
result = resolveRequestBodyForPostmanRequest(contextTest, operationItemTest);

expect(result.body.mode).to.equal('raw');
});

it('should return first-listed request body when preferredRequestBodyType is not a valid option', function () {
const contextTest = {
concreteUtils,
schemaCache: {},
schemaFakerCache: {},
computedOptions: {
parametersResolution: 'schema',
preferredRequestBodyType: 'foo-bar'
}
},
operationItemTest = operationItem.put,
result = resolveRequestBodyForPostmanRequest(contextTest, operationItemTest);

expect(result.body.mode).to.equal('raw');
});

it('should return encoded request body when preferredRequestBodyType is x-www-form-urlencoded', function () {
const contextTest = {
concreteUtils,
schemaCache: {},
schemaFakerCache: {},
computedOptions: {
parametersResolution: 'schema',
preferredRequestBodyType: 'x-www-form-urlencoded'
}
},
operationItemTest = operationItem.put,
result = resolveRequestBodyForPostmanRequest(contextTest, operationItemTest);

expect(result.body.mode).to.equal('urlencoded');
});

it('should return form data request body when preferredRequestBodyType is form-data', function () {
const contextTest = {
concreteUtils,
schemaCache: {},
schemaFakerCache: {},
computedOptions: {
parametersResolution: 'schema',
preferredRequestBodyType: 'form-data'
}
},
operationItemTest = operationItem.put,
result = resolveRequestBodyForPostmanRequest(contextTest, operationItemTest);

expect(result.body.mode).to.equal('formdata');
});

it('should return raw request body when preferredRequestBodyType is raw', function () {
const contextTest = {
concreteUtils,
schemaCache: {},
schemaFakerCache: {},
computedOptions: {
parametersResolution: 'schema',
preferredRequestBodyType: 'raw'
}
},
operationItemTest = operationItem.put,
result = resolveRequestBodyForPostmanRequest(contextTest, operationItemTest);

expect(result.body.mode).to.equal('raw');
});

it('should return raw request body when preferredRequestBodyType is first-listed', function () {
const contextTest = {
concreteUtils,
schemaCache: {},
schemaFakerCache: {},
computedOptions: {
parametersResolution: 'schema',
preferredRequestBodyType: 'first-listed'
}
},
operationItemTest = operationItem.put,
result = resolveRequestBodyForPostmanRequest(contextTest, operationItemTest);

expect(result.body.mode).to.equal('raw');
});

});

0 comments on commit fec6268

Please sign in to comment.