diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index d35703b9..57a2a3d4 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1258,23 +1258,75 @@ let QUERYPARAM = 'query', case 'form': if (explode && _.isObject(paramValue)) { const isArrayValue = _.isArray(paramValue); - - _.forEach(paramValue, (value, key) => { - pmParams.push({ - key: isArrayValue ? paramName : key, - value: (value === undefined ? '' : _.toString(value)), - description, - disabled + // Arrays + if (isArrayValue) { + _.forEach(paramValue, (value) => { + pmParams.push({ + key: paramName, + value: (value === undefined ? '' : _.toString(value)), + description, + disabled + }); }); - }); + return pmParams; + } - return pmParams; + // Objects: + // If declared properties exist, include only declared properties. + // Else, exclude top-level schema keys. + const schemaProps = _.get(param, 'schema.properties'); + const hasProps = _.isObject(schemaProps) && !_.isEmpty(schemaProps); + if (hasProps) { + _.forEach(paramValue, (value, key) => { + if (!_.has(schemaProps, key)) { return; } + pmParams.push({ + key, + value: (value === undefined ? '' : _.toString(value)), + description, + disabled + }); + }); + } + else { + const schemaKeys = Object.keys(_.get(param, 'schema', {})); + _.forEach(paramValue, (value, key) => { + if (schemaKeys.includes(key)) { return; } + pmParams.push({ + key, + value: (value === undefined ? '' : _.toString(value)), + description, + disabled + }); + }); + } + if (pmParams.length) { + return pmParams; + } } break; case 'deepObject': if (_.isObject(paramValue) && !_.isArray(paramValue)) { - let extractedParams = extractDeepObjectParams(paramValue, paramName); + let filteredValue; + const schemaProps = _.get(param, 'schema.properties'); + const hasProps = _.isObject(schemaProps) && !_.isEmpty(schemaProps); + if (hasProps) { + filteredValue = _.pick(paramValue, Object.keys(schemaProps)); + // Strip each declared property's own schema keys from its value before extraction + _.forEach(filteredValue, (propValue, propName) => { + if (_.isObject(propValue)) { + // taking only the keys that are declared in the schema + const propSchemaKeys = Object.keys(_.get(schemaProps, propName, {})); + filteredValue[propName] = _.omit(propValue, propSchemaKeys); + } + }); + } + else { + const schemaKeys = Object.keys(_.get(param, 'schema', {})); + filteredValue = _.omit(paramValue, schemaKeys); + } + + let extractedParams = extractDeepObjectParams(filteredValue, paramName); _.forEach(extractedParams, (extractedParam) => { pmParams.push({ @@ -1284,8 +1336,9 @@ let QUERYPARAM = 'query', disabled }); }); - - return pmParams; + if (pmParams.length) { + return pmParams; + } } else if (_.isArray(paramValue)) { isNotSerializable = true; diff --git a/test/unit/convertV2WithTypes.test.js b/test/unit/convertV2WithTypes.test.js index 38610dd9..03707fb8 100644 --- a/test/unit/convertV2WithTypes.test.js +++ b/test/unit/convertV2WithTypes.test.js @@ -174,6 +174,118 @@ describe('convertV2WithTypes', function() { }); }); + it('should not emit schema.deprecated as query param key for form+explode and keep original param', function(done) { + const oas = { + openapi: '3.0.0', + info: { title: 'Form Explode Deprecated Test', version: '1.0.0' }, + paths: { + '/pets': { + get: { + parameters: [ + { + name: 'qp', + in: 'query', + schema: { deprecated: true } + } + ], + responses: { '200': { description: 'ok' } } + } + } + } + }; + + Converter.convertV2WithTypes({ type: 'json', data: oas }, {}, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + const items = conversionResult.output[0].data.item; + const request = items[0].item ? items[0].item[0].request : items[0].request; + const query = request.url.query || []; + + expect(query.some((p) => { return p.key === 'deprecated'; })).to.equal(false); + expect(query.some((p) => { return p.key === 'qp'; })).to.equal(true); + done(); + }); + }); + + it('should not emit qp[deprecated] for deepObject and keep original param', function(done) { + const oas = { + openapi: '3.0.0', + info: { title: 'DeepObject Deprecated Test', version: '1.0.0' }, + paths: { + '/pets': { + get: { + parameters: [ + { + name: 'qp', + in: 'query', + style: 'deepObject', + explode: true, + schema: { deprecated: true } + } + ], + responses: { '200': { description: 'ok' } } + } + } + } + }; + + Converter.convertV2WithTypes({ type: 'json', data: oas }, {}, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + const items = conversionResult.output[0].data.item; + const request = items[0].item ? items[0].item[0].request : items[0].request; + const query = request.url.query || []; + + expect(query.some((p) => { return (/^qp\[deprecated\]/).test(p.key); })).to.equal(false); + expect(query.some((p) => { return p.key === 'qp'; })).to.equal(true); + done(); + }); + }); + + it('should include declared schema.properties (including keys like type) for form+explode', function(done) { + const oas = { + openapi: '3.0.0', + info: { title: 'Declared Properties Test', version: '1.0.0' }, + paths: { + '/pets': { + get: { + parameters: [ + { + name: 'qp', + in: 'query', + schema: { + type: 'object', + deprecated: true, + properties: { + name: { type: 'string' }, + type: { type: 'string', deprecated: true } + } + } + } + ], + responses: { '200': { description: 'ok' } } + } + } + } + }; + + Converter.convertV2WithTypes({ type: 'json', data: oas }, {}, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + + const items = conversionResult.output[0].data.item; + const request = items[0].item ? items[0].item[0].request : items[0].request; + const query = request.url.query || []; + + expect(query.some((p) => { return p.key === 'name'; })).to.equal(true); + expect(query.some((p) => { return p.key === 'type'; })).to.equal(true); + expect(query.some((p) => { return p.key === 'deprecated'; })).to.equal(false); + done(); + }); + }); + it('should resolve nested array and object schema types correctly in extractedTypes', function(done) { const example = { name: 'Buddy',