From cb4599a69febc394c05e8eda239463d527ac9254 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Thu, 28 Nov 2024 17:28:58 +0530 Subject: [PATCH 01/19] AB-290-WIP-extracting-type-information --- index.js | 11 ++ lib/schemapack.js | 5 +- libV2/index.js | 12 +- libV2/schemaUtils.js | 295 +++++++++++++++++++++++++++++++++++++------ package-lock.json | 50 +++++++- package.json | 3 +- 6 files changed, 326 insertions(+), 50 deletions(-) diff --git a/index.js b/index.js index 98fc7ffa..a8738501 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,17 @@ module.exports = { return cb(new UserError(_.get(schema, 'validationResult.reason', DEFAULT_INVALID_ERROR))); }, + convertV2WithTypes: function(input, options, cb) { + const enableTypeFetching = true; + var schema = new SchemaPack(input, options, MODULE_VERSION.V2, enableTypeFetching); + + if (schema.validated) { + return schema.convertV2(cb); + } + + return cb(new UserError(_.get(schema, 'validationResult.reason', DEFAULT_INVALID_ERROR))); + }, + validate: function (input) { var schema = new SchemaPack(input); return schema.validationResult; diff --git a/lib/schemapack.js b/lib/schemapack.js index e3bd8d17..aa3724f5 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -38,7 +38,7 @@ let path = require('path'), pathBrowserify = require('path-browserify'); class SchemaPack { - constructor (input, options = {}, moduleVersion = MODULE_VERSION.V1) { + constructor (input, options = {}, moduleVersion = MODULE_VERSION.V1, enableTypeFetching = false) { if (input.type === schemaUtils.MULTI_FILE_API_TYPE_ALLOWED_VALUE && input.data && input.data[0] && input.data[0].path) { input = schemaUtils.mapDetectRootFilesInputToFolderInput(input); @@ -57,7 +57,8 @@ class SchemaPack { actualStack: 0, numberOfRequests: 0 }; - + this.enableTypeFetching = enableTypeFetching; + this.extractedTypes = []; this.computedOptions = utils.mergeOptions( // predefined options _.keyBy(this.definedOptions, 'id'), diff --git a/libV2/index.js b/libV2/index.js index 1c3ce5af..229e1825 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -32,7 +32,8 @@ module.exports = { let preOrderTraversal = GraphLib.alg.preorder(collectionTree, 'root:collection'); - let collection = {}; + let collection = {}, + finalExtractedTypesList = []; /** * individually start generating the folder, request, collection @@ -91,16 +92,18 @@ module.exports = { // generate the request form the node let request = {}, collectionVariables = [], - requestObject = {}; + requestObject = {}, + extractedTypesList = []; try { - ({ request, collectionVariables } = resolvePostmanRequest(context, + ({ request, collectionVariables, extractedTypesList } = resolvePostmanRequest(context, context.openapi.paths[node.meta.path], node.meta.path, node.meta.method )); requestObject = generateRequestItemObject(request); + finalExtractedTypesList = finalExtractedTypesList.concat(extractedTypesList); } catch (error) { console.error(error); @@ -224,7 +227,8 @@ module.exports = { type: 'collection', data: collection }], - analytics: this.analytics || {} + analytics: this.analytics || {}, + extractedTypes: finalExtractedTypesList }); }, diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 77ba2f54..28c6098f 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1,5 +1,6 @@ const generateAuthForCollectionFromOpenAPI = require('./helpers/collection/generateAuthForCollectionFromOpenAPI'); const utils = require('./utils'); +const { v4: uuidv4 } = require('uuid'); const schemaFaker = require('../assets/json-schema-faker'), _ = require('lodash'), @@ -736,6 +737,46 @@ let QUERYPARAM = 'query', return schema; }, + /** process and resolves types from nested schema structure **/ + + processSchema = (resolvedSchema, parentRequired = new Set()) => { + if (resolvedSchema.type === 'object' && resolvedSchema.properties) { + const schemaDetails = { + type: resolvedSchema.type || 'unknown', + properties: {} + }, + requiredProperties = new Set(resolvedSchema.required || []); + for (let [key, prop] of Object.entries(resolvedSchema.properties)) { + const propertyDetails = { + type: prop.type || 'unknown', + required: requiredProperties.has(key) || parentRequired.has(key), + enum: prop.enum || null, + minLength: prop.minLength || null, + maxLength: prop.maxLength || null, + minimum: prop.minimum || null, + maximum: prop.maximum || null, + pattern: prop.pattern || null, + example: prop.example || null, + description: prop.description || null, + format: prop.format || null + }; + if (prop.$ref) { + propertyDetails.properties = processSchema(prop); + } + else if (prop.properties) { + let res = processSchema(prop).properties; + propertyDetails.properties = res; + } + + schemaDetails.properties[key] = propertyDetails; + } + return schemaDetails; + } + return { + type: resolvedSchema.type || 'unknown' + }; + }, + /** * Wrapper around _resolveSchema which resolves a given schema * @@ -750,12 +791,13 @@ let QUERYPARAM = 'query', * @returns {Object} Returns the object that satisfies the schema */ resolveSchema = (context, schema, - { stack = 0, resolveFor = CONVERSION, seenRef = {}, isResponseSchema = false } = {} + { stack = 0, resolveFor = CONVERSION, seenRef = {}, isResponseSchema = false, isBodySchema = false } = {} ) => { // reset readOnly and writeOnly prop cache before resolving schema to make sure we have fresh cache resetReadWritePropCache(context); - - let resolvedSchema = _resolveSchema(context, schema, stack, resolveFor, seenRef); + let resolvedSchema = _resolveSchema(context, schema, stack, resolveFor, seenRef), + resolvedSchemaTypes = [], + propertyDetails = {}; /** * If readOnly or writeOnly properties are present in the schema, we need to clone original schema first. @@ -780,6 +822,35 @@ let QUERYPARAM = 'query', }); } + // for fetching types from request and response body $ref and nested $ref + if (isBodySchema && context.enableTypeFetching) { + let properties = processSchema(resolvedSchema); + resolvedSchemaTypes.push(properties); + context.resolvedSchemaTypes = resolvedSchemaTypes; + } + // for fetching types for $ref in request params + else if (context.enableTypeFetching && isResponseSchema && resolvedSchema.properties) { + const requiredProperties = new Set(resolvedSchema.required || []); + for (let [key, prop] of Object.entries(resolvedSchema.properties || {})) { + let keyName = key, + properties = { + type: prop.type || 'unknown', + required: requiredProperties.has(key), + enum: prop.enum || null, + minLength: prop.minLength || null, + maxLength: prop.maxLength || null, + minimum: prop.minimum || null, + maximum: prop.maximum || null, + pattern: prop.pattern || null, + example: prop.example || null + }; + propertyDetails = { keyName, properties }; + if (keyName) { + resolvedSchemaTypes.push(propertyDetails); + } + } + context.resolvedSchemaTypes = resolvedSchemaTypes; + } return resolvedSchema; }, @@ -1414,7 +1485,11 @@ let QUERYPARAM = 'query', } if (requestBodySchema.$ref) { - requestBodySchema = resolveSchema(context, requestBodySchema, { isResponseSchema: isExampleBody }); + requestBodySchema = resolveSchema( + context, + requestBodySchema, + { isResponseSchema: isExampleBody, isBodySchema: true } + ); } /** @@ -1443,6 +1518,7 @@ let QUERYPARAM = 'query', * b: 2 * } */ + if (requestBodySchema.example !== undefined) { const shouldResolveValueKey = _.has(requestBodySchema.example, 'value') && _.keys(requestBodySchema.example).length <= 1; @@ -1463,7 +1539,10 @@ let QUERYPARAM = 'query', examples = requestBodySchema.examples || _.get(requestBodySchema, 'schema.examples'); requestBodySchema = requestBodySchema.schema || requestBodySchema; - requestBodySchema = resolveSchema(context, requestBodySchema, { isResponseSchema: isExampleBody }); + requestBodySchema = resolveSchema( + context, + requestBodySchema, + { isResponseSchema: isExampleBody, isBodySchema: true }); // If schema object has example defined, try to use that if no example is defiend at request body level if (example === undefined && _.get(requestBodySchema, 'example') !== undefined) { @@ -1488,7 +1567,10 @@ let QUERYPARAM = 'query', requestBodySchema = requestBodySchema.schema || requestBodySchema; if (requestBodySchema.$ref) { - requestBodySchema = resolveSchema(context, requestBodySchema, { isResponseSchema: isExampleBody }); + requestBodySchema = resolveSchema( + context, + requestBodySchema, + { isResponseSchema: isExampleBody, isBodySchema: true }); } if (isBodyTypeXML) { @@ -1580,7 +1662,7 @@ let QUERYPARAM = 'query', } if (_.has(requestBodyContent, 'schema.$ref')) { - requestBodyContent.schema = resolveSchema(context, requestBodyContent.schema); + requestBodyContent.schema = resolveSchema(context, requestBodyContent.schema, { isBodySchema: true }); } resolvedBody = resolveBodyData(context, requestBodyContent.schema)[0]; @@ -1649,7 +1731,7 @@ let QUERYPARAM = 'query', param; requestBodySchema = _.has(requestBodyContent, 'schema.$ref') ? - resolveSchema(context, requestBodyContent.schema) : + resolveSchema(context, requestBodyContent.schema, { isBodySchema: true }) : _.get(requestBodyContent, 'schema'); paramSchema = _.get(requestBodySchema, ['properties', key], {}); @@ -1801,7 +1883,7 @@ let QUERYPARAM = 'query', } if (requestBody.$ref) { - requestBody = resolveSchema(context, requestBody); + requestBody = resolveSchema(context, requestBody, { isBodySchema: true }); } requestContent = requestBody.content; @@ -1899,6 +1981,7 @@ let QUERYPARAM = 'query', resolveQueryParamsForPostmanRequest = (context, operationItem, method) => { const params = resolvePathItemParams(context, operationItem[method].parameters, operationItem.parameters), pmParams = [], + queryParamTypes = [], { includeDeprecated } = context.computedOptions; _.forEach(params, (param) => { @@ -1914,7 +1997,29 @@ let QUERYPARAM = 'query', return; } - let paramValue = resolveValueOfParameter(context, param); + let propertyDetails = {}, + properties = {}, + keyName, + paramValue = resolveValueOfParameter(context, param); + + if (param && param.schema) { + const { name, schema } = param; + keyName = name; + properties = { + type: schema.type || 'unknown', + enum: schema.enum || null, + minLength: schema.minLength || null, + maxLength: schema.maxLength || null, + minimum: schema.minimum || null, + maximum: schema.maximum || null, + pattern: schema.pattern || null, + example: schema.example || null + }; + } + propertyDetails = { keyName, properties }; + if (keyName && param.schema && param.schema.type) { + queryParamTypes.push(propertyDetails); + } if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, @@ -1927,14 +2032,16 @@ let QUERYPARAM = 'query', const deserialisedParams = serialiseParamsBasedOnStyle(context, param, paramValue); pmParams.push(...deserialisedParams); + }); - return pmParams; + return { queryParamTypes, queryParams: pmParams }; }, resolvePathParamsForPostmanRequest = (context, operationItem, method) => { const params = resolvePathItemParams(context, operationItem[method].parameters, operationItem.parameters), pmParams = []; + let pathParamTypes = []; _.forEach(params, (param) => { if (!_.isObject(param)) { @@ -1949,7 +2056,29 @@ let QUERYPARAM = 'query', return; } - let paramValue = resolveValueOfParameter(context, param); + let propertyDetails = {}, + properties = {}, + keyName, + paramValue = resolveValueOfParameter(context, param); + + if (param && param.schema) { + const { name, schema } = param; + keyName = name; + properties = { + type: schema.type || 'unknown', + enum: schema.enum || null, + minLength: schema.minLength || null, + maxLength: schema.maxLength || null, + minimum: schema.minimum || null, + maximum: schema.maximum || null, + pattern: schema.pattern || null, + example: schema.example || null + }; + } + propertyDetails = { keyName, properties }; + if (keyName && param.schema && param.schema.type) { + pathParamTypes.push(propertyDetails); + } if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, @@ -1963,8 +2092,7 @@ let QUERYPARAM = 'query', pmParams.push(...deserialisedParams); }); - - return pmParams; + return { pathParamTypes, pathParams: pmParams }; }, resolveNameForPostmanReqeust = (context, operationItem, requestUrl) => { @@ -1999,6 +2127,7 @@ let QUERYPARAM = 'query', const params = resolvePathItemParams(context, operationItem[method].parameters, operationItem.parameters), pmParams = [], { keepImplicitHeaders, includeDeprecated } = context.computedOptions; + let headerTypes = []; _.forEach(params, (param) => { if (!_.isObject(param)) { @@ -2017,7 +2146,30 @@ let QUERYPARAM = 'query', return; } - let paramValue = resolveValueOfParameter(context, param); + let propertyDetails = {}, + properties = {}, + keyName, + paramValue = resolveValueOfParameter(context, param); + + if (param && param.schema) { + const { name, schema } = param; + keyName = name; + properties = { + type: schema.type || 'unknown', + enum: schema.enum || null, + minLength: schema.minLength || null, + maxLength: schema.maxLength || null, + minimum: schema.minimum || null, + maximum: schema.maximum || null, + pattern: schema.pattern || null, + example: schema.example || null + }; + } + propertyDetails = { keyName, properties }; + + if (keyName && param.schema && param.schema.type) { + headerTypes.push(propertyDetails); + } if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, @@ -2031,8 +2183,7 @@ let QUERYPARAM = 'query', pmParams.push(...deserialisedParams); }); - - return pmParams; + return { headerTypes, headers: pmParams }; }, /** @@ -2060,7 +2211,7 @@ let QUERYPARAM = 'query', } if (responseBody.$ref) { - responseBody = resolveSchema(context, responseBody, { isResponseSchema: true }); + responseBody = resolveSchema(context, responseBody, { isResponseSchema: true, isBodySchema: true }); } responseContent = responseBody.content; @@ -2119,6 +2270,7 @@ let QUERYPARAM = 'query', resolveResponseHeaders = (context, responseHeaders) => { const headers = [], { includeDeprecated } = context.computedOptions; + let headerTypes = []; if (_.has(responseHeaders, '$ref')) { responseHeaders = resolveSchema(context, responseHeaders, { isResponseSchema: true }); @@ -2133,7 +2285,10 @@ let QUERYPARAM = 'query', return; } - let headerValue = resolveValueOfParameter(context, value, { isResponseSchema: true }); + let headerValue = resolveValueOfParameter(context, value, { isResponseSchema: true }), + propertyDetails = {}, + properties = {}, + keyName; if (typeof headerValue === 'number' || typeof headerValue === 'boolean') { // the SDK will keep the number-ness, @@ -2147,8 +2302,31 @@ let QUERYPARAM = 'query', serialisedHeader = serialiseParamsBasedOnStyle(context, headerData, headerValue, { isResponseSchema: true }); headers.push(...serialisedHeader); + + if (headerData && headerData.schema) { + const { name, schema } = headerData; + keyName = name; + properties = { + type: schema.type || 'unknown', + enum: schema.enum || null, + minLength: schema.minLength || null, + maxLength: schema.maxLength || null, + minimum: schema.minimum || null, + maximum: schema.maximum || null, + pattern: schema.pattern || null, + example: schema.example || null + }; + + } + propertyDetails = { keyName, properties }; + if (keyName && headerData.schema && headerData.schema.type) { + headerTypes.push(propertyDetails); + } }); + if (context.enableTypeFetching) { + context.resolvedSchemaTypes = headerTypes; + } return headers; }, @@ -2240,12 +2418,15 @@ let QUERYPARAM = 'query', requestContent, rawBodyType, headerFamily, - isBodyTypeXML; + isBodyTypeXML, + resolvedExamplesList = [], + resolvedHeadersList = [], + finalRespBlock = {}; // store all request examples which will be used for creation of examples with correct request and response matching if (typeof requestBody === 'object') { if (requestBody.$ref) { - requestBody = resolveSchema(context, requestBody, { isResponseSchema: true }); + requestBody = resolveSchema(context, requestBody, { isResponseSchema: true, isBodySchema: true }); } requestContent = requestBody.content; @@ -2265,7 +2446,7 @@ let QUERYPARAM = 'query', if (isBodyTypeXML) { let bodyData = getXMLExampleData(context, exampleData, resolveSchema(context, content.schema, - { isResponseSchema: true })); + { isResponseSchema: true, isBodySchema: true })); exampleObj.value = getXmlVersionContent(bodyData); } @@ -2283,13 +2464,28 @@ let QUERYPARAM = 'query', } _.forOwn(operationItem.responses, (responseObj, code) => { - let responseSchema = _.has(responseObj, '$ref') ? - resolveSchema(context, responseObj, { isResponseSchema: true }) : responseObj, + let responseSchema = _.has(responseObj, '$ref') ? ( + resolveSchema(context, responseObj, { isResponseSchema: true, isBodySchema: true })) : responseObj, { includeAuthInfoInExample } = context.computedOptions, auth = request.auth, - resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}, - headers = resolveResponseHeaders(context, responseSchema.headers); - + resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}; + resolvedExamplesList = context.resolvedSchemaTypes; + context.resolvedSchemaTypes = null; + // eslint-disable-next-line one-var + let headers = resolveResponseHeaders(context, responseSchema.headers), + bodyHeaderObj, + respBlock, + uuid; + resolvedHeadersList = context.resolvedSchemaTypes; + bodyHeaderObj = + resolvedExamplesList ? { + body: JSON.stringify(resolvedExamplesList[0], null, 2), + headers: JSON.stringify(resolvedHeadersList, null, 2) } : {}; + uuid = uuidv4(); + respBlock = { [uuid]: bodyHeaderObj }; + Object.assign(finalRespBlock, respBlock); + + context.resolvedSchemaTypes = null; _.forOwn(resolvedExamples, (resolvedExample = {}) => { let { body, contentHeader = [], bodyType, acceptHeader, name } = resolvedExample, resolvedRequestBody = _.get(resolvedExample, 'request.body'), @@ -2350,8 +2546,13 @@ let QUERYPARAM = 'query', responses.push(response); }); }); - - return { responses, acceptHeader: requestAcceptHeader }; + // console.log('finalRespBlock is ', JSON.stringify(finalRespBlock, null, 2)); + return { + responses, + acceptHeader: requestAcceptHeader, + resolvedExampleTypes: finalRespBlock, + resolvedHeadersList + }; }; module.exports = { @@ -2366,16 +2567,21 @@ module.exports = { let url = resolveUrlForPostmanRequest(path), baseUrlData = resolveBaseUrlForPostmanRequest(operationItem[method]), requestName = resolveNameForPostmanReqeust(context, operationItem[method], url), - queryParams = resolveQueryParamsForPostmanRequest(context, operationItem, method), - headers = resolveHeadersForPostmanRequest(context, operationItem, method), - pathParams = resolvePathParamsForPostmanRequest(context, operationItem, method), + { queryParamTypes, queryParams } = resolveQueryParamsForPostmanRequest(context, operationItem, method), + { headerTypes, headers } = resolveHeadersForPostmanRequest(context, operationItem, method), + { pathParamTypes, pathParams } = resolvePathParamsForPostmanRequest(context, operationItem, method), { pathVariables, collectionVariables } = filterCollectionAndPathVariables(url, pathParams), requestBody = resolveRequestBodyForPostmanRequest(context, operationItem[method]), + bodyTypes = context.resolvedSchemaTypes ? context.resolvedSchemaTypes[0] : {}, request, securitySchema = _.get(operationItem, [method, 'security']), authHelper = generateAuthForCollectionFromOpenAPI(context.openapi, securitySchema), - { alwaysInheritAuthentication } = context.computedOptions; - + { alwaysInheritAuthentication } = context.computedOptions, + methodPath, + requestBlock, + requestObj, + extractedTypesList = []; + context.resolvedSchemaTypes = null; headers.push(..._.get(requestBody, 'headers', [])); pathVariables.push(...baseUrlData.pathVariables); collectionVariables.push(...baseUrlData.collectionVariables); @@ -2396,7 +2602,23 @@ module.exports = { auth: alwaysInheritAuthentication ? undefined : authHelper }; - const { responses, acceptHeader } = resolveResponseForPostmanRequest(context, operationItem[method], request); + const unifiedRequestTypes = { + body: JSON.stringify(bodyTypes, null, 4), + headers: JSON.stringify(headerTypes, null, 4), + pathParam: JSON.stringify(pathParamTypes, null, 4), + queryParam: JSON.stringify(queryParamTypes, null, 4) + }, + + { + responses, + acceptHeader, + resolvedExampleTypes + } = resolveResponseForPostmanRequest(context, operationItem[method], request); + + methodPath = method + path; + requestBlock = { request: unifiedRequestTypes, response: resolvedExampleTypes }; + requestObj = { [methodPath]: requestBlock }; + extractedTypesList.push(requestObj); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { @@ -2410,7 +2632,8 @@ module.exports = { responses }) }, - collectionVariables + collectionVariables, + extractedTypesList }; }, diff --git a/package-lock.json b/package-lock.json index aa8f5528..d87f3f95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "path-browserify": "1.0.1", "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", + "uuid": "^11.0.3", "yaml": "1.10.2" }, "bin": { @@ -2794,6 +2795,16 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-processinfo/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4363,6 +4374,15 @@ "node": ">=10" } }, + "node_modules/postman-collection/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/postman-collection/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -5365,11 +5385,16 @@ "dev": true }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/validate.io-array": { @@ -7710,6 +7735,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8913,6 +8944,11 @@ "lru-cache": "^6.0.0" } }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -9689,9 +9725,9 @@ "dev": true }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==" }, "validate.io-array": { "version": "1.0.6", diff --git a/package.json b/package.json index 7283bc0e..d4ae2c3b 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "ajv-formats": "2.1.1", "async": "3.2.4", "commander": "2.20.3", + "graphlib": "2.1.8", "js-yaml": "4.1.0", "json-pointer": "0.6.2", "json-schema-merge-allof": "0.8.1", @@ -128,10 +129,10 @@ "neotraverse": "0.6.15", "oas-resolver-browser": "2.5.6", "object-hash": "3.0.0", - "graphlib": "2.1.8", "path-browserify": "1.0.1", "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", + "uuid": "^11.0.3", "yaml": "1.10.2" }, "author": "Postman Labs ", From aa645c1d6ae829e818c6a34c9eba012b49fc9e68 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Fri, 6 Dec 2024 03:24:35 +0530 Subject: [PATCH 02/19] AB-289-flag-fixes --- libV2/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libV2/index.js b/libV2/index.js index 229e1825..69736c5d 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -228,7 +228,7 @@ module.exports = { data: collection }], analytics: this.analytics || {}, - extractedTypes: finalExtractedTypesList + extractedTypes: context.enableTypeFetching ? finalExtractedTypesList : [] }); }, From b54b8fbef027c933c81acab87c20eb65af11e13c Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Sun, 8 Dec 2024 18:19:28 +0530 Subject: [PATCH 03/19] AB-289-added tests and fixes --- libV2/index.js | 15 ++- libV2/schemaUtils.js | 17 ++- test/unit/convertV2.test.js | 2 +- test/unit/convertV2WithTypes.test.js | 158 +++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 test/unit/convertV2WithTypes.test.js diff --git a/libV2/index.js b/libV2/index.js index 69736c5d..73193626 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -220,15 +220,24 @@ module.exports = { if (!_.isEmpty(collection.variable)) { collection.variable = _.uniqBy(collection.variable, 'key'); } - + if (context.enableTypeFetching) { + return cb(null, { + result: true, + output: [{ + type: 'collection', + data: collection + }], + analytics: this.analytics || {}, + extractedTypes: finalExtractedTypesList || [] + }); + } return cb(null, { result: true, output: [{ type: 'collection', data: collection }], - analytics: this.analytics || {}, - extractedTypes: context.enableTypeFetching ? finalExtractedTypesList : [] + analytics: this.analytics || {} }); }, diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 28c6098f..cf892fe4 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -767,11 +767,24 @@ let QUERYPARAM = 'query', let res = processSchema(prop).properties; propertyDetails.properties = res; } + else if (prop.type === 'array' && prop.items) { + propertyDetails.items = processSchema(prop.items); + } schemaDetails.properties[key] = propertyDetails; } return schemaDetails; } + else if (resolvedSchema.type === 'array' && resolvedSchema.items) { + // Handle array type schema + const arrayDetails = { + type: resolvedSchema.type || 'unknown', + items: processSchema(resolvedSchema.items) + }; + if (resolvedSchema.minItems !== undefined) { arrayDetails.minItems = resolvedSchema.minItems; } + if (resolvedSchema.maxItems !== undefined) { arrayDetails.maxItems = resolvedSchema.maxItems; } + return arrayDetails; + } return { type: resolvedSchema.type || 'unknown' }; @@ -2615,10 +2628,8 @@ module.exports = { resolvedExampleTypes } = resolveResponseForPostmanRequest(context, operationItem[method], request); - methodPath = method + path; requestBlock = { request: unifiedRequestTypes, response: resolvedExampleTypes }; - requestObj = { [methodPath]: requestBlock }; - extractedTypesList.push(requestObj); + extractedTypesList.push(requestBlock); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { diff --git a/test/unit/convertV2.test.js b/test/unit/convertV2.test.js index 985e3499..98038d10 100644 --- a/test/unit/convertV2.test.js +++ b/test/unit/convertV2.test.js @@ -205,9 +205,9 @@ describe('The convert v2 Function', function() { tooManyRefs, function(done) { var openapi = fs.readFileSync(tooManyRefs, 'utf8'); Converter.convertV2({ type: 'string', data: openapi }, { schemaFaker: true }, (err, conversionResult) => { - expect(err).to.be.null; expect(conversionResult.result).to.equal(true); + expect(conversionResult).to.not.have.property('extractedTypes'); expect(conversionResult.output.length).to.equal(1); expect(conversionResult.output[0].type).to.equal('collection'); expect(conversionResult.output[0].data).to.have.property('info'); diff --git a/test/unit/convertV2WithTypes.test.js b/test/unit/convertV2WithTypes.test.js new file mode 100644 index 00000000..c90a5c54 --- /dev/null +++ b/test/unit/convertV2WithTypes.test.js @@ -0,0 +1,158 @@ +/* eslint-disable one-var */ +const expect = require('chai').expect, + Converter = require('../../index.js'), + fs = require('fs'), + path = require('path'), + VALID_OPENAPI_PATH = '../data/valid_openapi', + Ajv = require('ajv'), + testSpec = path.join(__dirname, VALID_OPENAPI_PATH + '/test.json'), + testSpec1 = path.join(__dirname, VALID_OPENAPI_PATH + '/test1.json'), + readOnlyNestedSpec = + path.join(__dirname, VALID_OPENAPI_PATH, '/readOnlyNested.json'), + ajv = new Ajv({ allErrors: true, strict: false }), + transformSchema = (schema) => { + const properties = schema.properties, + rest = Object.keys(schema) + .filter((key) => { return key !== 'properties'; }) + .reduce((acc, key) => { + acc[key] = schema[key]; + return acc; + }, {}), + + transformedProperties = Object.entries(properties).reduce( + (acc, [key, value]) => { + acc[key] = { + type: value.type, + enum: value.enum !== null ? value.enum : undefined, + minLength: value.minLength !== null ? value.minLength : undefined, + maxLength: value.maxLength !== null ? value.maxLength : undefined, + minimum: value.minimum !== null ? value.minimum : undefined, + maximum: value.maximum !== null ? value.maximum : undefined, + pattern: value.pattern !== null ? value.pattern : undefined, + format: value.format !== null ? value.format : undefined + }; + return acc; + }, + {} + ), + + + transformedObject = Object.assign({}, rest, { properties: transformedProperties }); + + return transformedObject; + }; + + +describe('convertV2WithTypes should generate collection confirming to collection schema', function() { + + it('Should generate collection conforming to schema for and fail if not valid ' + + testSpec, function(done) { + var openapi = fs.readFileSync(testSpec, 'utf8'); + Converter.convertV2WithTypes({ type: 'string', data: openapi }, { schemaFaker: true }, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + done(); + }); + }); + + it('Should generate collection conforming to schema for and fail if not valid ' + + testSpec1, function(done) { + Converter.convertV2WithTypes( + { type: 'file', data: testSpec1 }, { requestNameSource: 'url' }, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.output.length).to.equal(1); + expect(conversionResult.output[0].type).to.equal('collection'); + expect(conversionResult.output[0].data).to.have.property('info'); + expect(conversionResult.output[0].data).to.have.property('item'); + + done(); + }); + }); +}); + + +describe('convertV2WithTypes', function() { + it('should contain extracted types' + testSpec1, function () { + Converter.convertV2WithTypes( + { type: 'file', data: testSpec1 }, { requestNameSource: 'url' }, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.result).to.equal(true); + expect(conversionResult.extractedTypes).to.not.be.undefined; + expect(conversionResult.extractedTypes.length).to.not.equal(0); + } + ); + }); + + it('should validate the schema' + testSpec1, function() { + const example = { + code: 200, + message: 'Success' + }; + Converter.convertV2WithTypes( + { type: 'file', data: testSpec1 }, { requestNameSource: 'url' }, (err, conversionResult) => { + + expect(err).to.be.null; + expect(conversionResult.extractedTypes).to.be.an('array').that.is.not.empty; + const element = conversionResult.extractedTypes[0]; + + expect(element).to.be.an('object').that.includes.keys('request'); + expect(element).to.be.an('object').that.includes.keys('response'); + const { response } = element; + expect(response).to.be.an('object').that.is.not.empty; + const [key, value] = Object.entries(response)[1]; + expect(key).to.be.a('string'); + const schema = JSON.parse(value.body), + transformedSchema = transformSchema(schema), + validate = ajv.compile(transformedSchema), + valid = validate(example); + + expect(value).to.have.property('body').that.is.a('string'); + + + expect(valid, `Validation failed for key: ${key} with errors: ${JSON.stringify(validate.errors)}`).to.be.true; + }); + }); + + it('should resolve nested array and object schema types correctly in extractedTypes', function(done) { + const example = { + name: 'Buddy', + pet: { + id: 123, + name: 'Charlie', + address: { + addressCode: { + code: 'A123' + }, + city: 'New York' + } + } + }, + openapi = fs.readFileSync(readOnlyNestedSpec, 'utf8'), + options = { schemaFaker: true, exampleParametersResolution: 'schema' }; + + Converter.convertV2WithTypes({ type: 'string', data: openapi }, options, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.extractedTypes).to.be.an('array').that.is.not.empty; + + // Validate the first extracted type + const element = conversionResult.extractedTypes[0]; + const { response } = element; + + // Get the schema from the response + const [key, value] = Object.entries(response)[0]; + expect(value).to.have.property('body').that.is.a('string'); + + const schema = JSON.parse(value.body), + transformedSchema = transformSchema(schema), + validate = ajv.compile(transformedSchema), + valid = validate(example); + expect(valid, `Validation failed for key: ${key} with errors: ${JSON.stringify(validate.errors)}`).to.be.true; + done(); + }); + }); +}); From abaf386580496502cd7d3c3d53e40e7a335fda2c Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Sun, 8 Dec 2024 20:52:33 +0530 Subject: [PATCH 04/19] AB-289-downgraded-uuid-dep --- package-lock.json | 50 ++++++++--------------------------------------- package.json | 2 +- 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index d87f3f95..fe2c8775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "path-browserify": "1.0.1", "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", - "uuid": "^11.0.3", + "uuid": "^8.3.2", "yaml": "1.10.2" }, "bin": { @@ -2795,16 +2795,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/istanbul-lib-processinfo/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4374,15 +4364,6 @@ "node": ">=10" } }, - "node_modules/postman-collection/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/postman-collection/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -5385,16 +5366,12 @@ "dev": true }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "license": "MIT", "bin": { - "uuid": "dist/esm/bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/validate.io-array": { @@ -7735,12 +7712,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8944,11 +8915,6 @@ "lru-cache": "^6.0.0" } }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -9725,9 +9691,9 @@ "dev": true }, "uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "validate.io-array": { "version": "1.0.6", diff --git a/package.json b/package.json index d4ae2c3b..ec61d953 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "path-browserify": "1.0.1", "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", - "uuid": "^11.0.3", + "uuid": "^8.3.2", "yaml": "1.10.2" }, "author": "Postman Labs ", From d8d93676c11eae88d77880db4d35f15d57d9fce8 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Wed, 11 Dec 2024 03:33:57 +0530 Subject: [PATCH 05/19] AB-289-added-method-path --- libV2/schemaUtils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index cf892fe4..1de3cb1a 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -2628,8 +2628,10 @@ module.exports = { resolvedExampleTypes } = resolveResponseForPostmanRequest(context, operationItem[method], request); + methodPath = method + path; requestBlock = { request: unifiedRequestTypes, response: resolvedExampleTypes }; - extractedTypesList.push(requestBlock); + requestObj = { [methodPath]: requestBlock }; + extractedTypesList.push(requestObj); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { From 7217383011c8e4ca6822c3c4ce76169b723814bf Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Sun, 15 Dec 2024 01:55:13 +0530 Subject: [PATCH 06/19] AB-289-added-code-reverted-uuid --- libV2/schemaUtils.js | 7 ++----- package-lock.json | 1 - package.json | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 1de3cb1a..5283bd97 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1,6 +1,5 @@ const generateAuthForCollectionFromOpenAPI = require('./helpers/collection/generateAuthForCollectionFromOpenAPI'); const utils = require('./utils'); -const { v4: uuidv4 } = require('uuid'); const schemaFaker = require('../assets/json-schema-faker'), _ = require('lodash'), @@ -2488,14 +2487,12 @@ let QUERYPARAM = 'query', let headers = resolveResponseHeaders(context, responseSchema.headers), bodyHeaderObj, respBlock, - uuid; - resolvedHeadersList = context.resolvedSchemaTypes; + resolvedHeadersList = context.resolvedSchemaTypes; bodyHeaderObj = resolvedExamplesList ? { body: JSON.stringify(resolvedExamplesList[0], null, 2), headers: JSON.stringify(resolvedHeadersList, null, 2) } : {}; - uuid = uuidv4(); - respBlock = { [uuid]: bodyHeaderObj }; + respBlock = { [code]: bodyHeaderObj }; Object.assign(finalRespBlock, respBlock); context.resolvedSchemaTypes = null; diff --git a/package-lock.json b/package-lock.json index fe2c8775..d92fd249 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "path-browserify": "1.0.1", "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", - "uuid": "^8.3.2", "yaml": "1.10.2" }, "bin": { diff --git a/package.json b/package.json index ec61d953..781bfa5b 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,6 @@ "path-browserify": "1.0.1", "postman-collection": "^4.4.0", "swagger2openapi": "7.0.8", - "uuid": "^8.3.2", "yaml": "1.10.2" }, "author": "Postman Labs ", From 5ec3f70d913927e2feadba1691f7fbd5ebbbcfb8 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Mon, 16 Dec 2024 18:38:20 +0530 Subject: [PATCH 07/19] AB-289-removed-undefined-props --- libV2/schemaUtils.js | 88 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 5283bd97..32d0c0a1 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -749,15 +749,15 @@ let QUERYPARAM = 'query', const propertyDetails = { type: prop.type || 'unknown', required: requiredProperties.has(key) || parentRequired.has(key), - enum: prop.enum || null, - minLength: prop.minLength || null, - maxLength: prop.maxLength || null, - minimum: prop.minimum || null, - maximum: prop.maximum || null, - pattern: prop.pattern || null, - example: prop.example || null, - description: prop.description || null, - format: prop.format || null + enum: prop.enum || undefined, + minLength: prop.minLength || undefined, + maxLength: prop.maxLength || undefined, + minimum: prop.minimum || undefined, + maximum: prop.maximum || undefined, + pattern: prop.pattern || undefined, + example: prop.example || undefined, + description: prop.description || undefined, + format: prop.format || undefined }; if (prop.$ref) { propertyDetails.properties = processSchema(prop); @@ -848,13 +848,13 @@ let QUERYPARAM = 'query', properties = { type: prop.type || 'unknown', required: requiredProperties.has(key), - enum: prop.enum || null, - minLength: prop.minLength || null, - maxLength: prop.maxLength || null, - minimum: prop.minimum || null, - maximum: prop.maximum || null, - pattern: prop.pattern || null, - example: prop.example || null + enum: prop.enum || undefined, + minLength: prop.minLength || undefined, + maxLength: prop.maxLength || undefined, + minimum: prop.minimum || undefined, + maximum: prop.maximum || undefined, + pattern: prop.pattern || undefined, + example: prop.example || undefined }; propertyDetails = { keyName, properties }; if (keyName) { @@ -2019,13 +2019,13 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', - enum: schema.enum || null, - minLength: schema.minLength || null, - maxLength: schema.maxLength || null, - minimum: schema.minimum || null, - maximum: schema.maximum || null, - pattern: schema.pattern || null, - example: schema.example || null + enum: schema.enum || undefined, + minLength: schema.minLength || undefined, + maxLength: schema.maxLength || undefined, + minimum: schema.minimum || undefined, + maximum: schema.maximum || undefined, + pattern: schema.pattern || undefined, + example: schema.example || undefined }; } propertyDetails = { keyName, properties }; @@ -2078,13 +2078,13 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', - enum: schema.enum || null, - minLength: schema.minLength || null, - maxLength: schema.maxLength || null, - minimum: schema.minimum || null, - maximum: schema.maximum || null, - pattern: schema.pattern || null, - example: schema.example || null + enum: schema.enum || undefined, + minLength: schema.minLength || undefined, + maxLength: schema.maxLength || undefined, + minimum: schema.minimum || undefined, + maximum: schema.maximum || undefined, + pattern: schema.pattern || undefined, + example: schema.example || undefined }; } propertyDetails = { keyName, properties }; @@ -2168,13 +2168,13 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', - enum: schema.enum || null, - minLength: schema.minLength || null, - maxLength: schema.maxLength || null, - minimum: schema.minimum || null, - maximum: schema.maximum || null, - pattern: schema.pattern || null, - example: schema.example || null + enum: schema.enum || undefined, + minLength: schema.minLength || undefined, + maxLength: schema.maxLength || undefined, + minimum: schema.minimum || undefined, + maximum: schema.maximum || undefined, + pattern: schema.pattern || undefined, + example: schema.example || undefined }; } propertyDetails = { keyName, properties }; @@ -2320,13 +2320,13 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', - enum: schema.enum || null, - minLength: schema.minLength || null, - maxLength: schema.maxLength || null, - minimum: schema.minimum || null, - maximum: schema.maximum || null, - pattern: schema.pattern || null, - example: schema.example || null + enum: schema.enum || undefined, + minLength: schema.minLength || undefined, + maxLength: schema.maxLength || undefined, + minimum: schema.minimum || undefined, + maximum: schema.maximum || undefined, + pattern: schema.pattern || undefined, + example: schema.example || undefined }; } From fdc0ca222af9a937576140e8cee9c5fd9761cd2a Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 17 Dec 2024 21:32:00 +0530 Subject: [PATCH 08/19] AB-289-optimised-structure-and fixed-test-cases --- libV2/index.js | 11 ++++--- libV2/schemaUtils.js | 8 ++--- test/unit/convertV2WithTypes.test.js | 48 ++++++++++++++-------------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/libV2/index.js b/libV2/index.js index 73193626..4f406d47 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -33,7 +33,7 @@ module.exports = { let preOrderTraversal = GraphLib.alg.preorder(collectionTree, 'root:collection'); let collection = {}, - finalExtractedTypesList = []; + finalExtractedTypesObject = {}; /** * individually start generating the folder, request, collection @@ -93,17 +93,18 @@ module.exports = { let request = {}, collectionVariables = [], requestObject = {}, - extractedTypesList = []; + extractedTypesObject = {}; try { - ({ request, collectionVariables, extractedTypesList } = resolvePostmanRequest(context, + ({ request, collectionVariables, extractedTypesObject } = resolvePostmanRequest(context, context.openapi.paths[node.meta.path], node.meta.path, node.meta.method )); requestObject = generateRequestItemObject(request); - finalExtractedTypesList = finalExtractedTypesList.concat(extractedTypesList); + finalExtractedTypesObject = Object.assign({}, finalExtractedTypesObject, extractedTypesObject); + } catch (error) { console.error(error); @@ -228,7 +229,7 @@ module.exports = { data: collection }], analytics: this.analytics || {}, - extractedTypes: finalExtractedTypesList || [] + extractedTypes: finalExtractedTypesObject || [] }); } return cb(null, { diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 32d0c0a1..de7b1673 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -2589,8 +2589,7 @@ module.exports = { { alwaysInheritAuthentication } = context.computedOptions, methodPath, requestBlock, - requestObj, - extractedTypesList = []; + extractedTypesObject = {}; context.resolvedSchemaTypes = null; headers.push(..._.get(requestBody, 'headers', [])); pathVariables.push(...baseUrlData.pathVariables); @@ -2627,8 +2626,7 @@ module.exports = { methodPath = method + path; requestBlock = { request: unifiedRequestTypes, response: resolvedExampleTypes }; - requestObj = { [methodPath]: requestBlock }; - extractedTypesList.push(requestObj); + Object.assign(extractedTypesObject, { [methodPath]: requestBlock }); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { @@ -2643,7 +2641,7 @@ module.exports = { }) }, collectionVariables, - extractedTypesList + extractedTypesObject }; }, diff --git a/test/unit/convertV2WithTypes.test.js b/test/unit/convertV2WithTypes.test.js index c90a5c54..d0c5756c 100644 --- a/test/unit/convertV2WithTypes.test.js +++ b/test/unit/convertV2WithTypes.test.js @@ -83,7 +83,7 @@ describe('convertV2WithTypes', function() { expect(err).to.be.null; expect(conversionResult.result).to.equal(true); expect(conversionResult.extractedTypes).to.not.be.undefined; - expect(conversionResult.extractedTypes.length).to.not.equal(0); + expect(Object.keys(conversionResult.extractedTypes).length).to.not.equal(0); } ); }); @@ -97,24 +97,25 @@ describe('convertV2WithTypes', function() { { type: 'file', data: testSpec1 }, { requestNameSource: 'url' }, (err, conversionResult) => { expect(err).to.be.null; - expect(conversionResult.extractedTypes).to.be.an('array').that.is.not.empty; - const element = conversionResult.extractedTypes[0]; - - expect(element).to.be.an('object').that.includes.keys('request'); - expect(element).to.be.an('object').that.includes.keys('response'); - const { response } = element; - expect(response).to.be.an('object').that.is.not.empty; - const [key, value] = Object.entries(response)[1]; - expect(key).to.be.a('string'); - const schema = JSON.parse(value.body), - transformedSchema = transformSchema(schema), - validate = ajv.compile(transformedSchema), - valid = validate(example); - - expect(value).to.have.property('body').that.is.a('string'); - - - expect(valid, `Validation failed for key: ${key} with errors: ${JSON.stringify(validate.errors)}`).to.be.true; + expect(conversionResult.extractedTypes).to.be.an('object').that.is.not.empty; + for (const [path, element] of Object.entries(conversionResult.extractedTypes)) { + expect(element).to.be.an('object').that.includes.keys('request'); + expect(element).to.be.an('object').that.includes.keys('response'); + expect(path).to.be.a('string'); + + const { response } = element; + expect(response).to.be.an('object').that.is.not.empty; + const [key, value] = Object.entries(response)[1]; + expect(key).to.be.a('string'); + + const schema = JSON.parse(value.body), + transformedSchema = transformSchema(schema), + validate = ajv.compile(transformedSchema), + valid = validate(example); + + expect(value).to.have.property('body').that.is.a('string'); + expect(valid, `Validation failed for key: ${key} with errors: ${JSON.stringify(validate.errors)}`).to.be.true; + } }); }); @@ -137,12 +138,10 @@ describe('convertV2WithTypes', function() { Converter.convertV2WithTypes({ type: 'string', data: openapi }, options, (err, conversionResult) => { expect(err).to.be.null; - expect(conversionResult.extractedTypes).to.be.an('array').that.is.not.empty; + expect(conversionResult.extractedTypes).to.be.an('object').that.is.not.empty; - // Validate the first extracted type - const element = conversionResult.extractedTypes[0]; + const element = Object.values(conversionResult.extractedTypes)[0]; const { response } = element; - // Get the schema from the response const [key, value] = Object.entries(response)[0]; expect(value).to.have.property('body').that.is.a('string'); @@ -153,6 +152,7 @@ describe('convertV2WithTypes', function() { valid = validate(example); expect(valid, `Validation failed for key: ${key} with errors: ${JSON.stringify(validate.errors)}`).to.be.true; done(); - }); + } + ); }); }); From 9b30f474c2e5479571045c36eb49323bfb69f39d Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Thu, 19 Dec 2024 14:10:25 +0530 Subject: [PATCH 09/19] AB-289-fixed required and depricated param --- libV2/schemaUtils.js | 32 ++++++++++++++++++++++------ test/unit/convertV2WithTypes.test.js | 1 + 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index de7b1673..4c7db38b 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -742,13 +742,15 @@ let QUERYPARAM = 'query', if (resolvedSchema.type === 'object' && resolvedSchema.properties) { const schemaDetails = { type: resolvedSchema.type || 'unknown', - properties: {} + properties: {}, + required: [] }, requiredProperties = new Set(resolvedSchema.required || []); + for (let [key, prop] of Object.entries(resolvedSchema.properties)) { const propertyDetails = { type: prop.type || 'unknown', - required: requiredProperties.has(key) || parentRequired.has(key), + deprecated: prop.deprecated || false, enum: prop.enum || undefined, minLength: prop.minLength || undefined, maxLength: prop.maxLength || undefined, @@ -759,19 +761,29 @@ let QUERYPARAM = 'query', description: prop.description || undefined, format: prop.format || undefined }; + + if (requiredProperties.has(key) || parentRequired.has(key)) { + schemaDetails.required.push(key); + } if (prop.$ref) { - propertyDetails.properties = processSchema(prop); + propertyDetails.properties = processSchema(prop, parentRequired = requiredProperties); } else if (prop.properties) { - let res = processSchema(prop).properties; - propertyDetails.properties = res; + let res = processSchema(prop); + propertyDetails.properties = res.properties; + if (res.required) { + propertyDetails.required = res.required; + } } else if (prop.type === 'array' && prop.items) { - propertyDetails.items = processSchema(prop.items); + propertyDetails.items = processSchema(prop.items, parentRequired = requiredProperties); } schemaDetails.properties[key] = propertyDetails; } + if (schemaDetails.required && schemaDetails.required.length === 0) { + schemaDetails.required = undefined; + } return schemaDetails; } else if (resolvedSchema.type === 'array' && resolvedSchema.items) { @@ -2019,6 +2031,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + required: param.required || false, + deprecated: param.deprecated || false, enum: schema.enum || undefined, minLength: schema.minLength || undefined, maxLength: schema.maxLength || undefined, @@ -2078,6 +2092,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + required: param.required || false, + deprecated: param.deprecated || false, enum: schema.enum || undefined, minLength: schema.minLength || undefined, maxLength: schema.maxLength || undefined, @@ -2168,6 +2184,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + required: param.required || false, + deprecated: param.deprecated || false, enum: schema.enum || undefined, minLength: schema.minLength || undefined, maxLength: schema.maxLength || undefined, @@ -2320,6 +2338,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + required: schema.required || false, + deprecated: schema.deprecated || false, enum: schema.enum || undefined, minLength: schema.minLength || undefined, maxLength: schema.maxLength || undefined, diff --git a/test/unit/convertV2WithTypes.test.js b/test/unit/convertV2WithTypes.test.js index d0c5756c..11dbbdff 100644 --- a/test/unit/convertV2WithTypes.test.js +++ b/test/unit/convertV2WithTypes.test.js @@ -23,6 +23,7 @@ const expect = require('chai').expect, (acc, [key, value]) => { acc[key] = { type: value.type, + deprecated: value.deprecated || false, enum: value.enum !== null ? value.enum : undefined, minLength: value.minLength !== null ? value.minLength : undefined, maxLength: value.maxLength !== null ? value.maxLength : undefined, From d2269678d7b40339c4ffb04e0f45a177469b8f59 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Fri, 20 Dec 2024 14:38:01 +0530 Subject: [PATCH 10/19] AB-289-fixed-format and default param --- lib/schemapack.js | 2 +- libV2/schemaUtils.js | 34 +++++++++------------------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/lib/schemapack.js b/lib/schemapack.js index aa3724f5..55a08cb0 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -58,7 +58,7 @@ class SchemaPack { numberOfRequests: 0 }; this.enableTypeFetching = enableTypeFetching; - this.extractedTypes = []; + this.resolvedSchemaTypes = []; this.computedOptions = utils.mergeOptions( // predefined options _.keyBy(this.definedOptions, 'id'), diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 4c7db38b..bfe0b109 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -820,8 +820,7 @@ let QUERYPARAM = 'query', // reset readOnly and writeOnly prop cache before resolving schema to make sure we have fresh cache resetReadWritePropCache(context); let resolvedSchema = _resolveSchema(context, schema, stack, resolveFor, seenRef), - resolvedSchemaTypes = [], - propertyDetails = {}; + resolvedSchemaTypes = []; /** * If readOnly or writeOnly properties are present in the schema, we need to clone original schema first. @@ -852,29 +851,6 @@ let QUERYPARAM = 'query', resolvedSchemaTypes.push(properties); context.resolvedSchemaTypes = resolvedSchemaTypes; } - // for fetching types for $ref in request params - else if (context.enableTypeFetching && isResponseSchema && resolvedSchema.properties) { - const requiredProperties = new Set(resolvedSchema.required || []); - for (let [key, prop] of Object.entries(resolvedSchema.properties || {})) { - let keyName = key, - properties = { - type: prop.type || 'unknown', - required: requiredProperties.has(key), - enum: prop.enum || undefined, - minLength: prop.minLength || undefined, - maxLength: prop.maxLength || undefined, - minimum: prop.minimum || undefined, - maximum: prop.maximum || undefined, - pattern: prop.pattern || undefined, - example: prop.example || undefined - }; - propertyDetails = { keyName, properties }; - if (keyName) { - resolvedSchemaTypes.push(propertyDetails); - } - } - context.resolvedSchemaTypes = resolvedSchemaTypes; - } return resolvedSchema; }, @@ -2031,6 +2007,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + format: schema.format || undefined, + default: schema.default || undefined, required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, @@ -2092,6 +2070,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + format: schema.format || undefined, + default: schema.default || undefined, required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, @@ -2184,6 +2164,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + format: schema.format || undefined, + default: schema.default || undefined, required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, @@ -2338,6 +2320,8 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type || 'unknown', + format: schema.format || undefined, + default: schema.default || undefined, required: schema.required || false, deprecated: schema.deprecated || false, enum: schema.enum || undefined, From 8ae2fa5fa9c5920a59dcb61ffc9da98f1c1805f3 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Mon, 6 Jan 2025 01:23:38 +0530 Subject: [PATCH 11/19] AB-289-resolved-comments --- libV2/index.js | 6 ++--- libV2/schemaUtils.js | 54 +++++++++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/libV2/index.js b/libV2/index.js index 4f406d47..90da5c53 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -93,17 +93,17 @@ module.exports = { let request = {}, collectionVariables = [], requestObject = {}, - extractedTypesObject = {}; + typesObject = {}; try { - ({ request, collectionVariables, extractedTypesObject } = resolvePostmanRequest(context, + ({ request, collectionVariables, typesObject } = resolvePostmanRequest(context, context.openapi.paths[node.meta.path], node.meta.path, node.meta.method )); requestObject = generateRequestItemObject(request); - finalExtractedTypesObject = Object.assign({}, finalExtractedTypesObject, extractedTypesObject); + finalExtractedTypesObject = Object.assign({}, finalExtractedTypesObject, typesObject); } catch (error) { diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index bfe0b109..e2f359b4 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -765,9 +765,6 @@ let QUERYPARAM = 'query', if (requiredProperties.has(key) || parentRequired.has(key)) { schemaDetails.required.push(key); } - if (prop.$ref) { - propertyDetails.properties = processSchema(prop, parentRequired = requiredProperties); - } else if (prop.properties) { let res = processSchema(prop); propertyDetails.properties = res.properties; @@ -1993,11 +1990,15 @@ let QUERYPARAM = 'query', param = resolveSchema(context, param); } + if (_.has(param.schema, '$ref')) { + param.schema = resolveSchema(context, param.schema); + } + if (param.in !== QUERYPARAM || (!includeDeprecated && param.deprecated)) { return; } - let propertyDetails = {}, + let queryParamTypeInfo = {}, properties = {}, keyName, paramValue = resolveValueOfParameter(context, param); @@ -2020,9 +2021,9 @@ let QUERYPARAM = 'query', example: schema.example || undefined }; } - propertyDetails = { keyName, properties }; + queryParamTypeInfo = { keyName, properties }; if (keyName && param.schema && param.schema.type) { - queryParamTypes.push(propertyDetails); + queryParamTypes.push(queryParamTypeInfo); } if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { @@ -2056,11 +2057,15 @@ let QUERYPARAM = 'query', param = resolveSchema(context, param); } + if (_.has(param.schema, '$ref')) { + param.schema = resolveSchema(context, param.schema); + } + if (param.in !== PATHPARAM) { return; } - let propertyDetails = {}, + let pathParamTypeInfo = {}, properties = {}, keyName, paramValue = resolveValueOfParameter(context, param); @@ -2083,9 +2088,9 @@ let QUERYPARAM = 'query', example: schema.example || undefined }; } - propertyDetails = { keyName, properties }; + pathParamTypeInfo = { keyName, properties }; if (keyName && param.schema && param.schema.type) { - pathParamTypes.push(propertyDetails); + pathParamTypes.push(pathParamTypeInfo); } if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { @@ -2146,6 +2151,10 @@ let QUERYPARAM = 'query', param = resolveSchema(context, param); } + if (_.has(param.schema, '$ref')) { + param.schema = resolveSchema(context, param.schema); + } + if (param.in !== HEADER || (!includeDeprecated && param.deprecated)) { return; } @@ -2154,7 +2163,7 @@ let QUERYPARAM = 'query', return; } - let propertyDetails = {}, + let headerTypeInfo = {}, properties = {}, keyName, paramValue = resolveValueOfParameter(context, param); @@ -2177,10 +2186,10 @@ let QUERYPARAM = 'query', example: schema.example || undefined }; } - propertyDetails = { keyName, properties }; + headerTypeInfo = { keyName, properties }; if (keyName && param.schema && param.schema.type) { - headerTypes.push(propertyDetails); + headerTypes.push(headerTypeInfo); } if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { @@ -2298,7 +2307,7 @@ let QUERYPARAM = 'query', } let headerValue = resolveValueOfParameter(context, value, { isResponseSchema: true }), - propertyDetails = {}, + headerTypeInfo = {}, properties = {}, keyName; @@ -2334,9 +2343,9 @@ let QUERYPARAM = 'query', }; } - propertyDetails = { keyName, properties }; + headerTypeInfo = { keyName, properties }; if (keyName && headerData.schema && headerData.schema.type) { - headerTypes.push(propertyDetails); + headerTypes.push(headerTypeInfo); } }); @@ -2560,7 +2569,6 @@ let QUERYPARAM = 'query', responses.push(response); }); }); - // console.log('finalRespBlock is ', JSON.stringify(finalRespBlock, null, 2)); return { responses, acceptHeader: requestAcceptHeader, @@ -2593,7 +2601,7 @@ module.exports = { { alwaysInheritAuthentication } = context.computedOptions, methodPath, requestBlock, - extractedTypesObject = {}; + typesObject = {}; context.resolvedSchemaTypes = null; headers.push(..._.get(requestBody, 'headers', [])); pathVariables.push(...baseUrlData.pathVariables); @@ -2616,10 +2624,10 @@ module.exports = { }; const unifiedRequestTypes = { - body: JSON.stringify(bodyTypes, null, 4), - headers: JSON.stringify(headerTypes, null, 4), - pathParam: JSON.stringify(pathParamTypes, null, 4), - queryParam: JSON.stringify(queryParamTypes, null, 4) + body: JSON.stringify(bodyTypes, null, 2), + headers: JSON.stringify(headerTypes, null, 2), + pathParam: JSON.stringify(pathParamTypes, null, 2), + queryParam: JSON.stringify(queryParamTypes, null, 2) }, { @@ -2630,7 +2638,7 @@ module.exports = { methodPath = method + path; requestBlock = { request: unifiedRequestTypes, response: resolvedExampleTypes }; - Object.assign(extractedTypesObject, { [methodPath]: requestBlock }); + Object.assign(typesObject, { [methodPath]: requestBlock }); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { @@ -2645,7 +2653,7 @@ module.exports = { }) }, collectionVariables, - extractedTypesObject + typesObject }; }, From 6cffffb7c38c746d87578613001377aaf622d6c5 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 7 Jan 2025 02:00:38 +0530 Subject: [PATCH 12/19] AB-289-removed-context-schema-store-and-resolved-comments --- lib/schemapack.js | 1 - libV2/index.js | 6 +- libV2/schemaUtils.js | 198 +++++++++++++++++++++++-------------------- 3 files changed, 111 insertions(+), 94 deletions(-) diff --git a/lib/schemapack.js b/lib/schemapack.js index 55a08cb0..551dd965 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -58,7 +58,6 @@ class SchemaPack { numberOfRequests: 0 }; this.enableTypeFetching = enableTypeFetching; - this.resolvedSchemaTypes = []; this.computedOptions = utils.mergeOptions( // predefined options _.keyBy(this.definedOptions, 'id'), diff --git a/libV2/index.js b/libV2/index.js index 90da5c53..ad8a2e84 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -33,7 +33,7 @@ module.exports = { let preOrderTraversal = GraphLib.alg.preorder(collectionTree, 'root:collection'); let collection = {}, - finalExtractedTypesObject = {}; + extractedTypesObject = {}; /** * individually start generating the folder, request, collection @@ -103,7 +103,7 @@ module.exports = { )); requestObject = generateRequestItemObject(request); - finalExtractedTypesObject = Object.assign({}, finalExtractedTypesObject, typesObject); + extractedTypesObject = Object.assign({}, extractedTypesObject, typesObject); } catch (error) { @@ -229,7 +229,7 @@ module.exports = { data: collection }], analytics: this.analytics || {}, - extractedTypes: finalExtractedTypesObject || [] + extractedTypes: extractedTypesObject || {} }); } return cb(null, { diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index e2f359b4..29661456 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -736,8 +736,13 @@ let QUERYPARAM = 'query', return schema; }, - /** process and resolves types from nested schema structure **/ - + /** + * Processes and resolves types from Nested JSON schema structure. + * + * @param {Object} resolvedSchema - The resolved JSON schema to process. + * @param {Set} [parentRequired=new Set()] - A set of parent-required property keys to inherit. + * @returns {Object} The processed schema details. + */ processSchema = (resolvedSchema, parentRequired = new Set()) => { if (resolvedSchema.type === 'object' && resolvedSchema.properties) { const schemaDetails = { @@ -750,12 +755,12 @@ let QUERYPARAM = 'query', for (let [key, prop] of Object.entries(resolvedSchema.properties)) { const propertyDetails = { type: prop.type || 'unknown', - deprecated: prop.deprecated || false, + deprecated: prop.deprecated, enum: prop.enum || undefined, - minLength: prop.minLength || undefined, - maxLength: prop.maxLength || undefined, - minimum: prop.minimum || undefined, - maximum: prop.maximum || undefined, + minLength: prop.minLength !== undefined ? prop.minLength : undefined, + maxLength: prop.maxLength !== undefined ? prop.maxLength : undefined, + minimum: prop.minimum !== undefined ? prop.minimum : undefined, + maximum: prop.maximum !== undefined ? prop.maximum : undefined, pattern: prop.pattern || undefined, example: prop.example || undefined, description: prop.description || undefined, @@ -765,7 +770,7 @@ let QUERYPARAM = 'query', if (requiredProperties.has(key) || parentRequired.has(key)) { schemaDetails.required.push(key); } - else if (prop.properties) { + if (prop.properties) { let res = processSchema(prop); propertyDetails.properties = res.properties; if (res.required) { @@ -784,7 +789,6 @@ let QUERYPARAM = 'query', return schemaDetails; } else if (resolvedSchema.type === 'array' && resolvedSchema.items) { - // Handle array type schema const arrayDetails = { type: resolvedSchema.type || 'unknown', items: processSchema(resolvedSchema.items) @@ -812,12 +816,11 @@ let QUERYPARAM = 'query', * @returns {Object} Returns the object that satisfies the schema */ resolveSchema = (context, schema, - { stack = 0, resolveFor = CONVERSION, seenRef = {}, isResponseSchema = false, isBodySchema = false } = {} + { stack = 0, resolveFor = CONVERSION, seenRef = {}, isResponseSchema = false } = {} ) => { // reset readOnly and writeOnly prop cache before resolving schema to make sure we have fresh cache resetReadWritePropCache(context); - let resolvedSchema = _resolveSchema(context, schema, stack, resolveFor, seenRef), - resolvedSchemaTypes = []; + let resolvedSchema = _resolveSchema(context, schema, stack, resolveFor, seenRef); /** * If readOnly or writeOnly properties are present in the schema, we need to clone original schema first. @@ -841,13 +844,6 @@ let QUERYPARAM = 'query', _.unset(resolvedSchema, utils.getJsonPathArray(key)); }); } - - // for fetching types from request and response body $ref and nested $ref - if (isBodySchema && context.enableTypeFetching) { - let properties = processSchema(resolvedSchema); - resolvedSchemaTypes.push(properties); - context.resolvedSchemaTypes = resolvedSchemaTypes; - } return resolvedSchema; }, @@ -1475,7 +1471,8 @@ let QUERYPARAM = 'query', bodyKey = isExampleBody ? 'response' : 'request', responseExamples, example, - examples; + examples, + resolvedSchemaTypes = []; if (_.isEmpty(requestBodySchema)) { return [{ [bodyKey]: bodyData }]; @@ -1485,7 +1482,7 @@ let QUERYPARAM = 'query', requestBodySchema = resolveSchema( context, requestBodySchema, - { isResponseSchema: isExampleBody, isBodySchema: true } + { isResponseSchema: isExampleBody } ); } @@ -1539,7 +1536,7 @@ let QUERYPARAM = 'query', requestBodySchema = resolveSchema( context, requestBodySchema, - { isResponseSchema: isExampleBody, isBodySchema: true }); + { isResponseSchema: isExampleBody }); // If schema object has example defined, try to use that if no example is defiend at request body level if (example === undefined && _.get(requestBodySchema, 'example') !== undefined) { @@ -1567,7 +1564,7 @@ let QUERYPARAM = 'query', requestBodySchema = resolveSchema( context, requestBodySchema, - { isResponseSchema: isExampleBody, isBodySchema: true }); + { isResponseSchema: isExampleBody }); } if (isBodyTypeXML) { @@ -1593,7 +1590,11 @@ let QUERYPARAM = 'query', } }); } - + if (context.enableTypeFetching && requestBodySchema.type !== undefined && + requestBodySchema.type !== 'unknown') { + let properties = processSchema(requestBodySchema); + resolvedSchemaTypes.push(properties); + } // This is to handle cases when the jsf throws errors on finding unsupported types/formats try { bodyData = fakeSchema(context, requestBodySchema, shouldGenerateFromExample); @@ -1642,7 +1643,7 @@ let QUERYPARAM = 'query', return generateExamples(context, responseExamples, matchedRequestBodyExamples, requestBodySchema, isBodyTypeXML); } - return [{ [bodyKey]: bodyData }]; + return [{ [bodyKey]: bodyData }, resolvedSchemaTypes[0]]; }, resolveUrlEncodedRequestBodyForPostmanRequest = (context, requestBodyContent) => { @@ -1652,17 +1653,21 @@ let QUERYPARAM = 'query', mode: 'urlencoded', urlencoded: urlEncodedParams }, - resolvedBody; + resolvedBody, + result, + resolvedSchemaTypeObject; if (_.isEmpty(requestBodyContent)) { return requestBodyData; } if (_.has(requestBodyContent, 'schema.$ref')) { - requestBodyContent.schema = resolveSchema(context, requestBodyContent.schema, { isBodySchema: true }); + requestBodyContent.schema = resolveSchema(context, requestBodyContent.schema); } - resolvedBody = resolveBodyData(context, requestBodyContent.schema)[0]; + result = resolveBodyData(context, requestBodyContent.schema); + resolvedBody = result[0]; + resolvedSchemaTypeObject = result[1]; resolvedBody && (bodyData = resolvedBody.request); const encoding = requestBodyContent.encoding || {}; @@ -1697,7 +1702,8 @@ let QUERYPARAM = 'query', headers: [{ key: 'Content-Type', value: URLENCODED - }] + }], + resolvedSchemaTypeObject }; }, @@ -1709,13 +1715,17 @@ let QUERYPARAM = 'query', mode: 'formdata', formdata: formDataParams }, - resolvedBody; + resolvedBody, + result, + resolvedSchemaTypeObject; if (_.isEmpty(requestBodyContent)) { return requestBodyData; } - resolvedBody = resolveBodyData(context, requestBodyContent.schema)[0]; + result = resolveBodyData(context, requestBodyContent.schema); + resolvedBody = result[0]; + resolvedSchemaTypeObject = result[1]; resolvedBody && (bodyData = resolvedBody.request); encoding = _.get(requestBodyContent, 'encoding', {}); @@ -1728,7 +1738,7 @@ let QUERYPARAM = 'query', param; requestBodySchema = _.has(requestBodyContent, 'schema.$ref') ? - resolveSchema(context, requestBodyContent.schema, { isBodySchema: true }) : + resolveSchema(context, requestBodyContent.schema) : _.get(requestBodyContent, 'schema'); paramSchema = _.get(requestBodySchema, ['properties', key], {}); @@ -1773,7 +1783,8 @@ let QUERYPARAM = 'query', headers: [{ key: 'Content-Type', value: FORM_DATA - }] + }], + resolvedSchemaTypeObject }; }, @@ -1818,7 +1829,9 @@ let QUERYPARAM = 'query', headerFamily, dataToBeReturned = {}, { concreteUtils } = context, - resolvedBody; + resolvedBody, + result, + resolvedSchemaTypeObject; headerFamily = getHeaderFamily(bodyType); @@ -1829,7 +1842,9 @@ let QUERYPARAM = 'query', } // Handling for Raw mode data else { - resolvedBody = resolveBodyData(context, requestContent[bodyType], bodyType)[0]; + result = resolveBodyData(context, requestContent[bodyType], bodyType); + resolvedBody = result[0]; + resolvedSchemaTypeObject = result[1]; resolvedBody && (bodyData = resolvedBody.request); if ((bodyType === TEXT_XML || bodyType === APP_XML || headerFamily === HEADER_TYPE.XML)) { @@ -1861,7 +1876,8 @@ let QUERYPARAM = 'query', headers: [{ key: 'Content-Type', value: bodyType - }] + }], + resolvedSchemaTypeObject }; }, @@ -1880,7 +1896,7 @@ let QUERYPARAM = 'query', } if (requestBody.$ref) { - requestBody = resolveSchema(context, requestBody, { isBodySchema: true }); + requestBody = resolveSchema(context, requestBody); } requestContent = requestBody.content; @@ -2013,10 +2029,10 @@ let QUERYPARAM = 'query', required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, - minLength: schema.minLength || undefined, - maxLength: schema.maxLength || undefined, - minimum: schema.minimum || undefined, - maximum: schema.maximum || undefined, + minLength: schema.minLength !== undefined ? schema.minLength : undefined, + maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, + minimum: schema.minimum !== undefined ? schema.minimum : undefined, + maximum: schema.maximum !== undefined ? schema.maximum : undefined, pattern: schema.pattern || undefined, example: schema.example || undefined }; @@ -2080,10 +2096,10 @@ let QUERYPARAM = 'query', required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, - minLength: schema.minLength || undefined, - maxLength: schema.maxLength || undefined, - minimum: schema.minimum || undefined, - maximum: schema.maximum || undefined, + minLength: schema.minLength !== undefined ? schema.minLength : undefined, + maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, + minimum: schema.minimum !== undefined ? schema.minimum : undefined, + maximum: schema.maximum !== undefined ? schema.maximum : undefined, pattern: schema.pattern || undefined, example: schema.example || undefined }; @@ -2178,10 +2194,10 @@ let QUERYPARAM = 'query', required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, - minLength: schema.minLength || undefined, - maxLength: schema.maxLength || undefined, - minimum: schema.minimum || undefined, - maximum: schema.maximum || undefined, + minLength: schema.minLength !== undefined ? schema.minLength : undefined, + maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, + minimum: schema.minimum !== undefined ? schema.minimum : undefined, + maximum: schema.maximum !== undefined ? schema.maximum : undefined, pattern: schema.pattern || undefined, example: schema.example || undefined }; @@ -2225,14 +2241,16 @@ let QUERYPARAM = 'query', acceptHeader, emptyResponse = [{ body: undefined - }]; + }], + resolvedResponseBodyResult, + resolvedResponseBodyTypes; if (_.isEmpty(responseBody)) { return emptyResponse; } if (responseBody.$ref) { - responseBody = resolveSchema(context, responseBody, { isResponseSchema: true, isBodySchema: true }); + responseBody = resolveSchema(context, responseBody, { isResponseSchema: true }); } responseContent = responseBody.content; @@ -2244,7 +2262,10 @@ let QUERYPARAM = 'query', bodyType = getRawBodyType(responseContent); headerFamily = getHeaderFamily(bodyType); - allBodyData = resolveBodyData(context, responseContent[bodyType], bodyType, true, code, requestBodyExamples); + resolvedResponseBodyResult = resolveBodyData( + context, responseContent[bodyType], bodyType, true, code, requestBodyExamples); + allBodyData = resolvedResponseBodyResult; + resolvedResponseBodyTypes = resolvedResponseBodyResult[1]; return _.map(allBodyData, (bodyData) => { let requestBodyData = bodyData.request, @@ -2283,7 +2304,8 @@ let QUERYPARAM = 'query', }], name: exampleName, bodyType, - acceptHeader + acceptHeader, + resolvedResponseBodyTypes: resolvedResponseBodyTypes }; }); }, @@ -2334,10 +2356,10 @@ let QUERYPARAM = 'query', required: schema.required || false, deprecated: schema.deprecated || false, enum: schema.enum || undefined, - minLength: schema.minLength || undefined, - maxLength: schema.maxLength || undefined, - minimum: schema.minimum || undefined, - maximum: schema.maximum || undefined, + minLength: schema.minLength !== undefined ? schema.minLength : undefined, + maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, + minimum: schema.minimum !== undefined ? schema.minimum : undefined, + maximum: schema.maximum !== undefined ? schema.maximum : undefined, pattern: schema.pattern || undefined, example: schema.example || undefined }; @@ -2349,10 +2371,7 @@ let QUERYPARAM = 'query', } }); - if (context.enableTypeFetching) { - context.resolvedSchemaTypes = headerTypes; - } - return headers; + return { resolvedHeaderTypes: headerTypes, headers }; }, getPreviewLangugaForResponseBody = (bodyType) => { @@ -2444,14 +2463,13 @@ let QUERYPARAM = 'query', rawBodyType, headerFamily, isBodyTypeXML, - resolvedExamplesList = [], - resolvedHeadersList = [], - finalRespBlock = {}; + resolvedExamplesObject = {}, + responseBlock = {}; // store all request examples which will be used for creation of examples with correct request and response matching if (typeof requestBody === 'object') { if (requestBody.$ref) { - requestBody = resolveSchema(context, requestBody, { isResponseSchema: true, isBodySchema: true }); + requestBody = resolveSchema(context, requestBody, { isResponseSchema: true }); } requestContent = requestBody.content; @@ -2471,7 +2489,7 @@ let QUERYPARAM = 'query', if (isBodyTypeXML) { let bodyData = getXMLExampleData(context, exampleData, resolveSchema(context, content.schema, - { isResponseSchema: true, isBodySchema: true })); + { isResponseSchema: true })); exampleObj.value = getXmlVersionContent(bodyData); } @@ -2490,25 +2508,24 @@ let QUERYPARAM = 'query', _.forOwn(operationItem.responses, (responseObj, code) => { let responseSchema = _.has(responseObj, '$ref') ? ( - resolveSchema(context, responseObj, { isResponseSchema: true, isBodySchema: true })) : responseObj, + resolveSchema(context, responseObj, { isResponseSchema: true })) : responseObj, { includeAuthInfoInExample } = context.computedOptions, auth = request.auth, resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}; - resolvedExamplesList = context.resolvedSchemaTypes; - context.resolvedSchemaTypes = null; + resolvedExamplesObject = resolvedExamples[0] && resolvedExamples[0].resolvedResponseBodyTypes; // eslint-disable-next-line one-var - let headers = resolveResponseHeaders(context, responseSchema.headers), - bodyHeaderObj, - respBlock, - resolvedHeadersList = context.resolvedSchemaTypes; - bodyHeaderObj = - resolvedExamplesList ? { - body: JSON.stringify(resolvedExamplesList[0], null, 2), - headers: JSON.stringify(resolvedHeadersList, null, 2) } : {}; - respBlock = { [code]: bodyHeaderObj }; - Object.assign(finalRespBlock, respBlock); - - context.resolvedSchemaTypes = null; + let { resolvedHeaderTypes, headers } = resolveResponseHeaders(context, responseSchema.headers), + responseBodyHeaderObj, + responseTypes; + responseBodyHeaderObj = resolvedExamplesObject ? + { + body: JSON.stringify(resolvedExamplesObject, null, 2), + headers: JSON.stringify(resolvedHeaderTypes, null, 2) + } : {}; + + responseTypes = { [code]: responseBodyHeaderObj }; + Object.assign(responseBlock, responseTypes); + _.forOwn(resolvedExamples, (resolvedExample = {}) => { let { body, contentHeader = [], bodyType, acceptHeader, name } = resolvedExample, resolvedRequestBody = _.get(resolvedExample, 'request.body'), @@ -2572,8 +2589,7 @@ let QUERYPARAM = 'query', return { responses, acceptHeader: requestAcceptHeader, - resolvedExampleTypes: finalRespBlock, - resolvedHeadersList + unifiedResponseTypes: responseBlock }; }; @@ -2594,15 +2610,17 @@ module.exports = { { pathParamTypes, pathParams } = resolvePathParamsForPostmanRequest(context, operationItem, method), { pathVariables, collectionVariables } = filterCollectionAndPathVariables(url, pathParams), requestBody = resolveRequestBodyForPostmanRequest(context, operationItem[method]), - bodyTypes = context.resolvedSchemaTypes ? context.resolvedSchemaTypes[0] : {}, + bodyTypes = requestBody && requestBody.resolvedSchemaTypeObject, request, securitySchema = _.get(operationItem, [method, 'security']), authHelper = generateAuthForCollectionFromOpenAPI(context.openapi, securitySchema), { alwaysInheritAuthentication } = context.computedOptions, - methodPath, + requestIdentifier, requestBlock, typesObject = {}; - context.resolvedSchemaTypes = null; + if (requestBody && requestBody.resolvedSchemaTypeObject) { + delete requestBody.resolvedSchemaTypeObject; + } headers.push(..._.get(requestBody, 'headers', [])); pathVariables.push(...baseUrlData.pathVariables); collectionVariables.push(...baseUrlData.collectionVariables); @@ -2633,12 +2651,12 @@ module.exports = { { responses, acceptHeader, - resolvedExampleTypes + unifiedResponseTypes } = resolveResponseForPostmanRequest(context, operationItem[method], request); - methodPath = method + path; - requestBlock = { request: unifiedRequestTypes, response: resolvedExampleTypes }; - Object.assign(typesObject, { [methodPath]: requestBlock }); + requestIdentifier = method + path; + requestBlock = { request: unifiedRequestTypes, response: unifiedResponseTypes }; + Object.assign(typesObject, { [requestIdentifier]: requestBlock }); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { From 10631752588ab3b9be034663a06d67e1d150f379 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 7 Jan 2025 10:52:16 +0530 Subject: [PATCH 13/19] AB-289-resolved-bug --- libV2/schemaUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 29661456..390177ad 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -2264,7 +2264,7 @@ let QUERYPARAM = 'query', resolvedResponseBodyResult = resolveBodyData( context, responseContent[bodyType], bodyType, true, code, requestBodyExamples); - allBodyData = resolvedResponseBodyResult; + allBodyData = [resolvedResponseBodyResult[0]]; resolvedResponseBodyTypes = resolvedResponseBodyResult[1]; return _.map(allBodyData, (bodyData) => { From e7abec2b2ca8b8723c5463415ce2648aa85d3334 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 7 Jan 2025 13:44:17 +0530 Subject: [PATCH 14/19] AB-289-resolved-all-bugs --- libV2/schemaUtils.js | 60 +++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 390177ad..47cc15ec 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1590,11 +1590,6 @@ let QUERYPARAM = 'query', } }); } - if (context.enableTypeFetching && requestBodySchema.type !== undefined && - requestBodySchema.type !== 'unknown') { - let properties = processSchema(requestBodySchema); - resolvedSchemaTypes.push(properties); - } // This is to handle cases when the jsf throws errors on finding unsupported types/formats try { bodyData = fakeSchema(context, requestBodySchema, shouldGenerateFromExample); @@ -1609,6 +1604,11 @@ let QUERYPARAM = 'query', } } } + if (context.enableTypeFetching && requestBodySchema.type !== undefined && + requestBodySchema.type !== 'unknown') { + let properties = processSchema(requestBodySchema); + resolvedSchemaTypes.push(properties); + } // Generate multiple examples when either request or response contains more than one example if ( @@ -1639,11 +1639,17 @@ let QUERYPARAM = 'query', if (_.isEmpty(matchedRequestBodyExamples)) { matchedRequestBodyExamples = requestBodyExamples; } - - return generateExamples(context, responseExamples, matchedRequestBodyExamples, requestBodySchema, isBodyTypeXML); + const generatedBody = generateExamples( + context, responseExamples, matchedRequestBodyExamples, requestBodySchema, isBodyTypeXML); + return { + generatedBody, + resolvedSchemaType: resolvedSchemaTypes[0] + }; } - - return [{ [bodyKey]: bodyData }, resolvedSchemaTypes[0]]; + return { + generatedBody: [{ [bodyKey]: bodyData }], + resolvedSchemaType: resolvedSchemaTypes[0] + }; }, resolveUrlEncodedRequestBodyForPostmanRequest = (context, requestBodyContent) => { @@ -1666,8 +1672,14 @@ let QUERYPARAM = 'query', } result = resolveBodyData(context, requestBodyContent.schema); - resolvedBody = result[0]; - resolvedSchemaTypeObject = result[1]; + resolvedBody = + result && Array.isArray(result.generatedBody) && result.generatedBody.length > 0 ? + result.generatedBody[0] : + undefined; + resolvedSchemaTypeObject = + result && result.resolvedSchemaType !== undefined ? + result.resolvedSchemaType : + undefined; resolvedBody && (bodyData = resolvedBody.request); const encoding = requestBodyContent.encoding || {}; @@ -1724,8 +1736,14 @@ let QUERYPARAM = 'query', } result = resolveBodyData(context, requestBodyContent.schema); - resolvedBody = result[0]; - resolvedSchemaTypeObject = result[1]; + resolvedBody = + result && Array.isArray(result.generatedBody) && result.generatedBody.length > 0 ? + result.generatedBody[0] : + undefined; + resolvedSchemaTypeObject = + result && result.resolvedSchemaType !== undefined ? + result.resolvedSchemaType : + undefined; resolvedBody && (bodyData = resolvedBody.request); encoding = _.get(requestBodyContent, 'encoding', {}); @@ -1843,8 +1861,14 @@ let QUERYPARAM = 'query', // Handling for Raw mode data else { result = resolveBodyData(context, requestContent[bodyType], bodyType); - resolvedBody = result[0]; - resolvedSchemaTypeObject = result[1]; + resolvedBody = + result && Array.isArray(result.generatedBody) && result.generatedBody.length > 0 ? + result.generatedBody[0] : + undefined; + resolvedSchemaTypeObject = + result && result.resolvedSchemaType !== undefined ? + result.resolvedSchemaType : + undefined; resolvedBody && (bodyData = resolvedBody.request); if ((bodyType === TEXT_XML || bodyType === APP_XML || headerFamily === HEADER_TYPE.XML)) { @@ -2264,8 +2288,10 @@ let QUERYPARAM = 'query', resolvedResponseBodyResult = resolveBodyData( context, responseContent[bodyType], bodyType, true, code, requestBodyExamples); - allBodyData = [resolvedResponseBodyResult[0]]; - resolvedResponseBodyTypes = resolvedResponseBodyResult[1]; + allBodyData = resolvedResponseBodyResult.generatedBody; + resolvedResponseBodyTypes = resolvedResponseBodyResult ? + resolvedResponseBodyResult.resolvedSchemaType : + undefined; return _.map(allBodyData, (bodyData) => { let requestBodyData = bodyData.request, From c4a95916bcac8bfa3097747101e78d9b50ca9a9b Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 7 Jan 2025 14:41:52 +0530 Subject: [PATCH 15/19] AB-289-resolved-all-comments --- libV2/schemaUtils.js | 70 +++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 47cc15ec..2bd844c2 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -762,8 +762,8 @@ let QUERYPARAM = 'query', minimum: prop.minimum !== undefined ? prop.minimum : undefined, maximum: prop.maximum !== undefined ? prop.maximum : undefined, pattern: prop.pattern || undefined, - example: prop.example || undefined, - description: prop.description || undefined, + example: prop.example !== undefined ? prop.example : undefined, + description: prop.description !== undefined ? prop.description : undefined, format: prop.format || undefined }; @@ -2015,6 +2015,23 @@ let QUERYPARAM = 'query', return reqParam; }, + createProperties = (schema, param) => { + return { + type: schema.type || 'unknown', + format: schema.format || undefined, + default: schema.default !== undefined ? schema.default : undefined, + required: param.required || false, + deprecated: param.deprecated || false, + enum: schema.enum || undefined, + minLength: schema.minLength !== undefined ? schema.minLength : undefined, + maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, + minimum: schema.minimum !== undefined ? schema.minimum : undefined, + maximum: schema.maximum !== undefined ? schema.maximum : undefined, + pattern: schema.pattern || undefined, + example: schema.example !== undefined ? schema.example : undefined + }; + }, + resolveQueryParamsForPostmanRequest = (context, operationItem, method) => { const params = resolvePathItemParams(context, operationItem[method].parameters, operationItem.parameters), pmParams = [], @@ -2046,20 +2063,7 @@ let QUERYPARAM = 'query', if (param && param.schema) { const { name, schema } = param; keyName = name; - properties = { - type: schema.type || 'unknown', - format: schema.format || undefined, - default: schema.default || undefined, - required: param.required || false, - deprecated: param.deprecated || false, - enum: schema.enum || undefined, - minLength: schema.minLength !== undefined ? schema.minLength : undefined, - maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, - minimum: schema.minimum !== undefined ? schema.minimum : undefined, - maximum: schema.maximum !== undefined ? schema.maximum : undefined, - pattern: schema.pattern || undefined, - example: schema.example || undefined - }; + properties = createProperties(schema, param); } queryParamTypeInfo = { keyName, properties }; if (keyName && param.schema && param.schema.type) { @@ -2113,20 +2117,7 @@ let QUERYPARAM = 'query', if (param && param.schema) { const { name, schema } = param; keyName = name; - properties = { - type: schema.type || 'unknown', - format: schema.format || undefined, - default: schema.default || undefined, - required: param.required || false, - deprecated: param.deprecated || false, - enum: schema.enum || undefined, - minLength: schema.minLength !== undefined ? schema.minLength : undefined, - maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, - minimum: schema.minimum !== undefined ? schema.minimum : undefined, - maximum: schema.maximum !== undefined ? schema.maximum : undefined, - pattern: schema.pattern || undefined, - example: schema.example || undefined - }; + properties = createProperties(schema, param); } pathParamTypeInfo = { keyName, properties }; if (keyName && param.schema && param.schema.type) { @@ -2211,20 +2202,7 @@ let QUERYPARAM = 'query', if (param && param.schema) { const { name, schema } = param; keyName = name; - properties = { - type: schema.type || 'unknown', - format: schema.format || undefined, - default: schema.default || undefined, - required: param.required || false, - deprecated: param.deprecated || false, - enum: schema.enum || undefined, - minLength: schema.minLength !== undefined ? schema.minLength : undefined, - maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, - minimum: schema.minimum !== undefined ? schema.minimum : undefined, - maximum: schema.maximum !== undefined ? schema.maximum : undefined, - pattern: schema.pattern || undefined, - example: schema.example || undefined - }; + properties = createProperties(schema, param); } headerTypeInfo = { keyName, properties }; @@ -2378,7 +2356,7 @@ let QUERYPARAM = 'query', properties = { type: schema.type || 'unknown', format: schema.format || undefined, - default: schema.default || undefined, + default: schema.default !== undefined ? schema.default : undefined, required: schema.required || false, deprecated: schema.deprecated || false, enum: schema.enum || undefined, @@ -2387,7 +2365,7 @@ let QUERYPARAM = 'query', minimum: schema.minimum !== undefined ? schema.minimum : undefined, maximum: schema.maximum !== undefined ? schema.maximum : undefined, pattern: schema.pattern || undefined, - example: schema.example || undefined + example: schema.example !== undefined ? schema.example : undefined }; } From a8de5ffbc51580b09cfdcdf8e6bdf4e6efdc66a4 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 7 Jan 2025 18:38:06 +0530 Subject: [PATCH 16/19] AB-289-refactored-code --- libV2/index.js | 6 ++-- libV2/schemaUtils.js | 65 +++++++++++++++++++------------------------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/libV2/index.js b/libV2/index.js index ad8a2e84..8f552f89 100644 --- a/libV2/index.js +++ b/libV2/index.js @@ -93,17 +93,17 @@ module.exports = { let request = {}, collectionVariables = [], requestObject = {}, - typesObject = {}; + requestTypesObject = {}; try { - ({ request, collectionVariables, typesObject } = resolvePostmanRequest(context, + ({ request, collectionVariables, requestTypesObject } = resolvePostmanRequest(context, context.openapi.paths[node.meta.path], node.meta.path, node.meta.method )); requestObject = generateRequestItemObject(request); - extractedTypesObject = Object.assign({}, extractedTypesObject, typesObject); + extractedTypesObject = Object.assign({}, extractedTypesObject, requestTypesObject); } catch (error) { diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 2bd844c2..da59a0a1 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -743,7 +743,7 @@ let QUERYPARAM = 'query', * @param {Set} [parentRequired=new Set()] - A set of parent-required property keys to inherit. * @returns {Object} The processed schema details. */ - processSchema = (resolvedSchema, parentRequired = new Set()) => { + processSchema = (resolvedSchema) => { if (resolvedSchema.type === 'object' && resolvedSchema.properties) { const schemaDetails = { type: resolvedSchema.type || 'unknown', @@ -767,7 +767,7 @@ let QUERYPARAM = 'query', format: prop.format || undefined }; - if (requiredProperties.has(key) || parentRequired.has(key)) { + if (requiredProperties.has(key)) { schemaDetails.required.push(key); } if (prop.properties) { @@ -778,7 +778,7 @@ let QUERYPARAM = 'query', } } else if (prop.type === 'array' && prop.items) { - propertyDetails.items = processSchema(prop.items, parentRequired = requiredProperties); + propertyDetails.items = processSchema(prop.items); } schemaDetails.properties[key] = propertyDetails; @@ -2119,6 +2119,7 @@ let QUERYPARAM = 'query', keyName = name; properties = createProperties(schema, param); } + pathParamTypeInfo = { keyName, properties }; if (keyName && param.schema && param.schema.type) { pathParamTypes.push(pathParamTypeInfo); @@ -2136,6 +2137,7 @@ let QUERYPARAM = 'query', pmParams.push(...deserialisedParams); }); + return { pathParamTypes, pathParams: pmParams }; }, @@ -2199,16 +2201,14 @@ let QUERYPARAM = 'query', keyName, paramValue = resolveValueOfParameter(context, param); - if (param && param.schema) { + if (param && param.name && param.schema && param.schema.type) { const { name, schema } = param; keyName = name; properties = createProperties(schema, param); } headerTypeInfo = { keyName, properties }; - if (keyName && param.schema && param.schema.type) { - headerTypes.push(headerTypeInfo); - } + headerTypes.push(headerTypeInfo); if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, @@ -2222,6 +2222,7 @@ let QUERYPARAM = 'query', pmParams.push(...deserialisedParams); }); + return { headerTypes, headers: pmParams }; }, @@ -2267,9 +2268,7 @@ let QUERYPARAM = 'query', resolvedResponseBodyResult = resolveBodyData( context, responseContent[bodyType], bodyType, true, code, requestBodyExamples); allBodyData = resolvedResponseBodyResult.generatedBody; - resolvedResponseBodyTypes = resolvedResponseBodyResult ? - resolvedResponseBodyResult.resolvedSchemaType : - undefined; + resolvedResponseBodyTypes = resolvedResponseBodyResult.resolvedSchemaType; return _.map(allBodyData, (bodyData) => { let requestBodyData = bodyData.request, @@ -2350,11 +2349,11 @@ let QUERYPARAM = 'query', headers.push(...serialisedHeader); - if (headerData && headerData.schema) { + if (headerData && headerData.name && headerData.schema && headerData.schema.type) { const { name, schema } = headerData; keyName = name; properties = { - type: schema.type || 'unknown', + type: schema.type, format: schema.format || undefined, default: schema.default !== undefined ? schema.default : undefined, required: schema.required || false, @@ -2370,9 +2369,7 @@ let QUERYPARAM = 'query', } headerTypeInfo = { keyName, properties }; - if (keyName && headerData.schema && headerData.schema.type) { - headerTypes.push(headerTypeInfo); - } + headerTypes.push(headerTypeInfo); }); return { resolvedHeaderTypes: headerTypes, headers }; @@ -2468,7 +2465,7 @@ let QUERYPARAM = 'query', headerFamily, isBodyTypeXML, resolvedExamplesObject = {}, - responseBlock = {}; + responseTypes = {}; // store all request examples which will be used for creation of examples with correct request and response matching if (typeof requestBody === 'object') { @@ -2515,20 +2512,18 @@ let QUERYPARAM = 'query', resolveSchema(context, responseObj, { isResponseSchema: true })) : responseObj, { includeAuthInfoInExample } = context.computedOptions, auth = request.auth, - resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}; + resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}, + { resolvedHeaderTypes, headers } = resolveResponseHeaders(context, responseSchema.headers), + responseBodyHeaderObj; resolvedExamplesObject = resolvedExamples[0] && resolvedExamples[0].resolvedResponseBodyTypes; // eslint-disable-next-line one-var - let { resolvedHeaderTypes, headers } = resolveResponseHeaders(context, responseSchema.headers), - responseBodyHeaderObj, - responseTypes; - responseBodyHeaderObj = resolvedExamplesObject ? + responseBodyHeaderObj = { body: JSON.stringify(resolvedExamplesObject, null, 2), headers: JSON.stringify(resolvedHeaderTypes, null, 2) - } : {}; + }; - responseTypes = { [code]: responseBodyHeaderObj }; - Object.assign(responseBlock, responseTypes); + Object.assign(responseTypes, { [code]: responseBodyHeaderObj }); _.forOwn(resolvedExamples, (resolvedExample = {}) => { let { body, contentHeader = [], bodyType, acceptHeader, name } = resolvedExample, @@ -2593,7 +2588,7 @@ let QUERYPARAM = 'query', return { responses, acceptHeader: requestAcceptHeader, - unifiedResponseTypes: responseBlock + responseTypes: responseTypes }; }; @@ -2614,17 +2609,13 @@ module.exports = { { pathParamTypes, pathParams } = resolvePathParamsForPostmanRequest(context, operationItem, method), { pathVariables, collectionVariables } = filterCollectionAndPathVariables(url, pathParams), requestBody = resolveRequestBodyForPostmanRequest(context, operationItem[method]), - bodyTypes = requestBody && requestBody.resolvedSchemaTypeObject, + requestBodyTypes = requestBody && requestBody.resolvedSchemaTypeObject, request, securitySchema = _.get(operationItem, [method, 'security']), authHelper = generateAuthForCollectionFromOpenAPI(context.openapi, securitySchema), { alwaysInheritAuthentication } = context.computedOptions, requestIdentifier, - requestBlock, - typesObject = {}; - if (requestBody && requestBody.resolvedSchemaTypeObject) { - delete requestBody.resolvedSchemaTypeObject; - } + requestTypesObject = {}; headers.push(..._.get(requestBody, 'headers', [])); pathVariables.push(...baseUrlData.pathVariables); collectionVariables.push(...baseUrlData.collectionVariables); @@ -2645,8 +2636,8 @@ module.exports = { auth: alwaysInheritAuthentication ? undefined : authHelper }; - const unifiedRequestTypes = { - body: JSON.stringify(bodyTypes, null, 2), + const requestTypes = { + body: JSON.stringify(requestBodyTypes, null, 2), headers: JSON.stringify(headerTypes, null, 2), pathParam: JSON.stringify(pathParamTypes, null, 2), queryParam: JSON.stringify(queryParamTypes, null, 2) @@ -2655,12 +2646,12 @@ module.exports = { { responses, acceptHeader, - unifiedResponseTypes + responseTypes } = resolveResponseForPostmanRequest(context, operationItem[method], request); requestIdentifier = method + path; - requestBlock = { request: unifiedRequestTypes, response: unifiedResponseTypes }; - Object.assign(typesObject, { [requestIdentifier]: requestBlock }); + Object.assign(requestTypesObject, + { [requestIdentifier]: { request: requestTypes, response: responseTypes } }); // add accept header if found and not present already if (!_.isEmpty(acceptHeader)) { @@ -2675,7 +2666,7 @@ module.exports = { }) }, collectionVariables, - typesObject + requestTypesObject }; }, From 6f302941437f196cd9f119d63dcb768a8e254bf6 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Tue, 7 Jan 2025 21:47:51 +0530 Subject: [PATCH 17/19] AB-289-refactored-code-and-added-test-case --- libV2/schemaUtils.js | 188 ++++++++++++++------------- test/unit/convertV2WithTypes.test.js | 132 +++++++++++++++++++ 2 files changed, 232 insertions(+), 88 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index da59a0a1..1aae098a 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -739,49 +739,51 @@ let QUERYPARAM = 'query', /** * Processes and resolves types from Nested JSON schema structure. * - * @param {Object} resolvedSchema - The resolved JSON schema to process. - * @param {Set} [parentRequired=new Set()] - A set of parent-required property keys to inherit. + * @param {Object} resolvedSchema - The resolved JSON schema to process for type extraction. * @returns {Object} The processed schema details. */ processSchema = (resolvedSchema) => { if (resolvedSchema.type === 'object' && resolvedSchema.properties) { const schemaDetails = { - type: resolvedSchema.type || 'unknown', + type: resolvedSchema.type, properties: {}, required: [] }, requiredProperties = new Set(resolvedSchema.required || []); - for (let [key, prop] of Object.entries(resolvedSchema.properties)) { + for (let [propName, propValue] of Object.entries(resolvedSchema.properties)) { + if (!propValue.type) { + continue; + } const propertyDetails = { - type: prop.type || 'unknown', - deprecated: prop.deprecated, - enum: prop.enum || undefined, - minLength: prop.minLength !== undefined ? prop.minLength : undefined, - maxLength: prop.maxLength !== undefined ? prop.maxLength : undefined, - minimum: prop.minimum !== undefined ? prop.minimum : undefined, - maximum: prop.maximum !== undefined ? prop.maximum : undefined, - pattern: prop.pattern || undefined, - example: prop.example !== undefined ? prop.example : undefined, - description: prop.description !== undefined ? prop.description : undefined, - format: prop.format || undefined + type: propValue.type, + deprecated: propValue.deprecated, + enum: propValue.enum || undefined, + minLength: propValue.minLength, + maxLength: propValue.maxLength, + minimum: propValue.minimum, + maximum: propValue.maximum, + pattern: propValue.pattern, + example: propValue.example, + description: propValue.description, + format: propValue.format }; - if (requiredProperties.has(key)) { - schemaDetails.required.push(key); + if (requiredProperties.has(propName)) { + schemaDetails.required.push(propName); } - if (prop.properties) { - let res = processSchema(prop); - propertyDetails.properties = res.properties; - if (res.required) { - propertyDetails.required = res.required; + if (propValue.properties) { + let processedProperties = processSchema(propValue); + propertyDetails.properties = processedProperties.properties; + if (processedProperties.required) { + propertyDetails.required = processedProperties.required; } } - else if (prop.type === 'array' && prop.items) { - propertyDetails.items = processSchema(prop.items); + else if (propValue.type === 'array' && propValue.items) { + propertyDetails.items = processSchema(propValue.items); } - schemaDetails.properties[key] = propertyDetails; + schemaDetails.properties[propName] = propertyDetails; } if (schemaDetails.required && schemaDetails.required.length === 0) { schemaDetails.required = undefined; @@ -790,7 +792,7 @@ let QUERYPARAM = 'query', } else if (resolvedSchema.type === 'array' && resolvedSchema.items) { const arrayDetails = { - type: resolvedSchema.type || 'unknown', + type: resolvedSchema.type, items: processSchema(resolvedSchema.items) }; if (resolvedSchema.minItems !== undefined) { arrayDetails.minItems = resolvedSchema.minItems; } @@ -798,7 +800,7 @@ let QUERYPARAM = 'query', return arrayDetails; } return { - type: resolvedSchema.type || 'unknown' + type: resolvedSchema.type }; }, @@ -820,6 +822,7 @@ let QUERYPARAM = 'query', ) => { // reset readOnly and writeOnly prop cache before resolving schema to make sure we have fresh cache resetReadWritePropCache(context); + let resolvedSchema = _resolveSchema(context, schema, stack, resolveFor, seenRef); /** @@ -844,6 +847,7 @@ let QUERYPARAM = 'query', _.unset(resolvedSchema, utils.getJsonPathArray(key)); }); } + return resolvedSchema; }, @@ -1590,6 +1594,7 @@ let QUERYPARAM = 'query', } }); } + // This is to handle cases when the jsf throws errors on finding unsupported types/formats try { bodyData = fakeSchema(context, requestBodySchema, shouldGenerateFromExample); @@ -1603,11 +1608,12 @@ let QUERYPARAM = 'query', bodyData = ''; } } + } - if (context.enableTypeFetching && requestBodySchema.type !== undefined && - requestBodySchema.type !== 'unknown') { - let properties = processSchema(requestBodySchema); - resolvedSchemaTypes.push(properties); + + if (context.enableTypeFetching && requestBodySchema.type !== undefined) { + const requestBodySchemaTypes = processSchema(requestBodySchema); + resolvedSchemaTypes.push(requestBodySchemaTypes); } // Generate multiple examples when either request or response contains more than one example @@ -1639,13 +1645,16 @@ let QUERYPARAM = 'query', if (_.isEmpty(matchedRequestBodyExamples)) { matchedRequestBodyExamples = requestBodyExamples; } + const generatedBody = generateExamples( context, responseExamples, matchedRequestBodyExamples, requestBodySchema, isBodyTypeXML); + return { generatedBody, resolvedSchemaType: resolvedSchemaTypes[0] }; } + return { generatedBody: [{ [bodyKey]: bodyData }], resolvedSchemaType: resolvedSchemaTypes[0] @@ -1660,7 +1669,7 @@ let QUERYPARAM = 'query', urlencoded: urlEncodedParams }, resolvedBody, - result, + resolvedBodyResult, resolvedSchemaTypeObject; if (_.isEmpty(requestBodyContent)) { @@ -1671,15 +1680,16 @@ let QUERYPARAM = 'query', requestBodyContent.schema = resolveSchema(context, requestBodyContent.schema); } - result = resolveBodyData(context, requestBodyContent.schema); + resolvedBodyResult = resolveBodyData(context, requestBodyContent.schema); resolvedBody = - result && Array.isArray(result.generatedBody) && result.generatedBody.length > 0 ? - result.generatedBody[0] : - undefined; - resolvedSchemaTypeObject = - result && result.resolvedSchemaType !== undefined ? - result.resolvedSchemaType : + + resolvedBodyResult && Array.isArray(resolvedBodyResult.generatedBody) && + resolvedBodyResult.generatedBody[0] ? + resolvedBodyResult.generatedBody[0] : undefined; + + resolvedSchemaTypeObject = resolvedBodyResult && + resolvedBodyResult.resolvedSchemaType ? resolvedBodyResult.resolvedSchemaType : undefined; resolvedBody && (bodyData = resolvedBody.request); const encoding = requestBodyContent.encoding || {}; @@ -1728,22 +1738,25 @@ let QUERYPARAM = 'query', formdata: formDataParams }, resolvedBody, - result, + resolvedBodyResult, resolvedSchemaTypeObject; if (_.isEmpty(requestBodyContent)) { return requestBodyData; } - result = resolveBodyData(context, requestBodyContent.schema); + resolvedBodyResult = resolveBodyData(context, requestBodyContent.schema); resolvedBody = - result && Array.isArray(result.generatedBody) && result.generatedBody.length > 0 ? - result.generatedBody[0] : - undefined; + resolvedBodyResult && Array.isArray(resolvedBodyResult.generatedBody) && + resolvedBodyResult.generatedBody[0] ? + resolvedBodyResult.generatedBody[0] : + undefined; + resolvedSchemaTypeObject = - result && result.resolvedSchemaType !== undefined ? + resolvedBodyResult && resolvedBodyResult.resolvedSchemaType ? result.resolvedSchemaType : undefined; + resolvedBody && (bodyData = resolvedBody.request); encoding = _.get(requestBodyContent, 'encoding', {}); @@ -1848,7 +1861,7 @@ let QUERYPARAM = 'query', dataToBeReturned = {}, { concreteUtils } = context, resolvedBody, - result, + resolvedBodyResult, resolvedSchemaTypeObject; headerFamily = getHeaderFamily(bodyType); @@ -1860,15 +1873,18 @@ let QUERYPARAM = 'query', } // Handling for Raw mode data else { - result = resolveBodyData(context, requestContent[bodyType], bodyType); + resolvedBodyResult = resolveBodyData(context, requestContent[bodyType], bodyType); resolvedBody = - result && Array.isArray(result.generatedBody) && result.generatedBody.length > 0 ? - result.generatedBody[0] : - undefined; + resolvedBodyResult && Array.isArray(resolvedBodyResult.generatedBody) && + resolvedBodyResult.generatedBody[0] ? + resolvedBodyResult.generatedBody[0] : + undefined; + resolvedSchemaTypeObject = - result && result.resolvedSchemaType !== undefined ? - result.resolvedSchemaType : + resolvedBodyResult && resolvedBodyResult.resolvedSchemaType ? + resolvedBodyResult.resolvedSchemaType : undefined; + resolvedBody && (bodyData = resolvedBody.request); if ((bodyType === TEXT_XML || bodyType === APP_XML || headerFamily === HEADER_TYPE.XML)) { @@ -2015,20 +2031,21 @@ let QUERYPARAM = 'query', return reqParam; }, - createProperties = (schema, param) => { + createProperties = (param) => { + const { schema } = param; return { - type: schema.type || 'unknown', - format: schema.format || undefined, - default: schema.default !== undefined ? schema.default : undefined, + type: schema.type, + format: schema.format, + default: schema.default, required: param.required || false, deprecated: param.deprecated || false, enum: schema.enum || undefined, - minLength: schema.minLength !== undefined ? schema.minLength : undefined, - maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, - minimum: schema.minimum !== undefined ? schema.minimum : undefined, - maximum: schema.maximum !== undefined ? schema.maximum : undefined, - pattern: schema.pattern || undefined, - example: schema.example !== undefined ? schema.example : undefined + minLength: schema.minLength, + maxLength: schema.maxLength, + minimum: schema.minimum, + maximum: schema.maximum, + pattern: schema.pattern, + example: schema.example }; }, @@ -2060,15 +2077,14 @@ let QUERYPARAM = 'query', keyName, paramValue = resolveValueOfParameter(context, param); - if (param && param.schema) { - const { name, schema } = param; - keyName = name; - properties = createProperties(schema, param); + if (param && param.name && param.schema && param.schema.type) { + keyName = param.name; + properties = createProperties(param); } + queryParamTypeInfo = { keyName, properties }; - if (keyName && param.schema && param.schema.type) { - queryParamTypes.push(queryParamTypeInfo); - } + + queryParamTypes.push(queryParamTypeInfo); if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, @@ -2114,16 +2130,13 @@ let QUERYPARAM = 'query', keyName, paramValue = resolveValueOfParameter(context, param); - if (param && param.schema) { - const { name, schema } = param; - keyName = name; - properties = createProperties(schema, param); + if (param && param.name && param.schema && param.schema.type) { + keyName = param.name; + properties = createProperties(param); } pathParamTypeInfo = { keyName, properties }; - if (keyName && param.schema && param.schema.type) { - pathParamTypes.push(pathParamTypeInfo); - } + pathParamTypes.push(pathParamTypeInfo); if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, @@ -2202,10 +2215,10 @@ let QUERYPARAM = 'query', paramValue = resolveValueOfParameter(context, param); if (param && param.name && param.schema && param.schema.type) { - const { name, schema } = param; - keyName = name; - properties = createProperties(schema, param); + keyName = param.name; + properties = createProperties(param); } + headerTypeInfo = { keyName, properties }; headerTypes.push(headerTypeInfo); @@ -2354,17 +2367,17 @@ let QUERYPARAM = 'query', keyName = name; properties = { type: schema.type, - format: schema.format || undefined, - default: schema.default !== undefined ? schema.default : undefined, + format: schema.format, + default: schema.default, required: schema.required || false, deprecated: schema.deprecated || false, enum: schema.enum || undefined, - minLength: schema.minLength !== undefined ? schema.minLength : undefined, - maxLength: schema.maxLength !== undefined ? schema.maxLength : undefined, - minimum: schema.minimum !== undefined ? schema.minimum : undefined, - maximum: schema.maximum !== undefined ? schema.maximum : undefined, - pattern: schema.pattern || undefined, - example: schema.example !== undefined ? schema.example : undefined + minLength: schema.minLength, + maxLength: schema.maxLength, + minimum: schema.minimum, + maximum: schema.maximum, + pattern: schema.pattern, + example: schema.example }; } @@ -2516,7 +2529,6 @@ let QUERYPARAM = 'query', { resolvedHeaderTypes, headers } = resolveResponseHeaders(context, responseSchema.headers), responseBodyHeaderObj; resolvedExamplesObject = resolvedExamples[0] && resolvedExamples[0].resolvedResponseBodyTypes; - // eslint-disable-next-line one-var responseBodyHeaderObj = { body: JSON.stringify(resolvedExamplesObject, null, 2), diff --git a/test/unit/convertV2WithTypes.test.js b/test/unit/convertV2WithTypes.test.js index 11dbbdff..f4f629b3 100644 --- a/test/unit/convertV2WithTypes.test.js +++ b/test/unit/convertV2WithTypes.test.js @@ -1,4 +1,9 @@ +/* eslint-disable max-len */ +// Disabling max Length for better visibility of the expectedExtractedTypes + /* eslint-disable one-var */ +// Disabling as we want the checks to run in order of their declaration + const expect = require('chai').expect, Converter = require('../../index.js'), fs = require('fs'), @@ -60,6 +65,47 @@ describe('convertV2WithTypes should generate collection confirming to collection }); }); + it('should validate parameters of the collection', function (done) { + const openapi = fs.readFileSync(testSpec1, 'utf8'), + options = { schemaFaker: true, exampleParametersResolution: 'schema' }; + + Converter.convertV2WithTypes({ type: 'string', data: openapi }, options, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.output).to.be.an('array').that.is.not.empty; + + const firstFolder = conversionResult.output[0].data.item[0]; + expect(firstFolder).to.have.property('name', 'pets'); + + const listAllPets = firstFolder.item[0]; + expect(listAllPets).to.have.property('name', 'List all pets'); + expect(listAllPets.request.method).to.equal('GET'); + + const createPet = firstFolder.item[1]; + expect(createPet).to.have.property('name', '/pets'); + expect(createPet.request.method).to.equal('POST'); + expect(createPet.request.body.mode).to.equal('raw'); + expect(createPet.request.body.raw).to.include('request body comes here'); + + const queryParams = listAllPets.request.url.query; + expect(queryParams).to.be.an('array').that.has.length(3); + expect(queryParams[0]).to.have.property('key', 'limit'); + expect(queryParams[0]).to.have.property('value', ''); + + const headers = listAllPets.request.header; + expect(headers).to.be.an('array').that.is.not.empty; + expect(headers[0]).to.have.property('key', 'variable'); + expect(headers[0]).to.have.property('value', ','); + + const response = listAllPets.response[0]; + expect(response).to.have.property('status', 'OK'); + expect(response).to.have.property('code', 200); + expect(response.body).to.include('"id": ""'); + + done(); + } + ); + }); + it('Should generate collection conforming to schema for and fail if not valid ' + testSpec1, function(done) { Converter.convertV2WithTypes( @@ -156,4 +202,90 @@ describe('convertV2WithTypes', function() { } ); }); + + it('should resolve extractedTypes into correct schema structure', function(done) { + const expectedExtractedTypes = { + 'get/pets': { + 'request': { + 'headers': '[\n {\n "keyName": "variable",\n "properties": {\n "type": "array",\n "required": false,\n "deprecated": false\n }\n }\n]', + 'pathParam': '[]', + 'queryParam': '[\n {\n "keyName": "limit",\n "properties": {\n "type": "string",\n "default": "",\n "required": false,\n "deprecated": false\n }\n },\n {\n "keyName": "variable2",\n "properties": {\n "type": "array",\n "required": false,\n "deprecated": false\n }\n },\n {\n "keyName": "variable3",\n "properties": {\n "type": "array",\n "required": false,\n "deprecated": false\n }\n }\n]' + }, + 'response': { + '200': { + 'body': '{\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "integer",\n "format": "int64"\n },\n "name": {\n "type": "string"\n },\n "tag": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "name"\n ]\n }\n}', + 'headers': '[\n {\n "keyName": "x-next",\n "properties": {\n "type": "string",\n "default": "",\n "required": false,\n "deprecated": false\n }\n }\n]' + }, + 'default': { + 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', + 'headers': '[]' + } + } + }, + 'post/pets': { + 'request': { + 'headers': '[]', + 'pathParam': '[]', + 'queryParam': '[\n {\n "keyName": "limit",\n "properties": {\n "type": "string",\n "default": "",\n "required": false,\n "deprecated": false\n }\n },\n {\n "keyName": "variable3",\n "properties": {\n "type": "array",\n "required": false,\n "deprecated": false\n }\n }\n]' + }, + 'response': { + '201': { + 'headers': '[]' + }, + 'default': { + 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', + 'headers': '[\n {\n "properties": {}\n }\n]' + } + } + }, + 'get/pet/{petId}': { + 'request': { + 'headers': '[]', + 'pathParam': '[\n {\n "keyName": "petId",\n "properties": {\n "type": "string",\n "default": "",\n "required": true,\n "deprecated": false\n }\n }\n]', + 'queryParam': '[]' + }, + 'response': { + '200': { + 'body': '{\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "integer",\n "format": "int64"\n },\n "name": {\n "type": "string"\n },\n "tag": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "name"\n ]\n }\n}', + 'headers': '[]' + }, + 'default': { + 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', + 'headers': '[]' + } + } + }, + 'post/pet/{petId}': { + 'request': { + 'headers': '[]', + 'pathParam': '[\n {\n "keyName": "petId",\n "properties": {\n "type": "string",\n "default": "",\n "required": true,\n "deprecated": false\n }\n }\n]', + 'queryParam': '[]' + }, + 'response': { + '200': { + 'body': '{\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "integer",\n "format": "int64"\n },\n "name": {\n "type": "string"\n },\n "tag": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "name"\n ]\n }\n}', + 'headers': '[]' + }, + 'default': { + 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', + 'headers': '[]' + } + } + } + }, + openapi = fs.readFileSync(testSpec1, 'utf8'), + options = { schemaFaker: true, exampleParametersResolution: 'schema' }; + + Converter.convertV2WithTypes({ type: 'string', data: openapi }, options, (err, conversionResult) => { + expect(err).to.be.null; + expect(conversionResult.extractedTypes).to.be.an('object').that.is.not.empty; + + const extractedTypes = conversionResult.extractedTypes; + expect(JSON.parse(JSON.stringify(extractedTypes))).to.deep.equal( + JSON.parse(JSON.stringify(expectedExtractedTypes))); + done(); + } + ); + }); + }); From dc4a48ab47d6fd2d1f8d7e0802b04d9584bc3858 Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Wed, 8 Jan 2025 17:02:13 +0530 Subject: [PATCH 18/19] AB-289-refactored-code --- libV2/schemaUtils.js | 90 ++++++++++++---------------- libV2/utils.js | 4 -- test/unit/convertV2WithTypes.test.js | 19 +++--- 3 files changed, 48 insertions(+), 65 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 1aae098a..45b6ec1d 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -1682,14 +1682,12 @@ let QUERYPARAM = 'query', resolvedBodyResult = resolveBodyData(context, requestBodyContent.schema); resolvedBody = + resolvedBodyResult && + Array.isArray(resolvedBodyResult.generatedBody) && + resolvedBodyResult.generatedBody[0]; - resolvedBodyResult && Array.isArray(resolvedBodyResult.generatedBody) && - resolvedBodyResult.generatedBody[0] ? - resolvedBodyResult.generatedBody[0] : - undefined; + resolvedSchemaTypeObject = resolvedBodyResult && resolvedBodyResult.resolvedSchemaType; - resolvedSchemaTypeObject = resolvedBodyResult && - resolvedBodyResult.resolvedSchemaType ? resolvedBodyResult.resolvedSchemaType : undefined; resolvedBody && (bodyData = resolvedBody.request); const encoding = requestBodyContent.encoding || {}; @@ -1747,15 +1745,11 @@ let QUERYPARAM = 'query', resolvedBodyResult = resolveBodyData(context, requestBodyContent.schema); resolvedBody = - resolvedBodyResult && Array.isArray(resolvedBodyResult.generatedBody) && - resolvedBodyResult.generatedBody[0] ? - resolvedBodyResult.generatedBody[0] : - undefined; + resolvedBodyResult && + Array.isArray(resolvedBodyResult.generatedBody) && + resolvedBodyResult.generatedBody[0]; - resolvedSchemaTypeObject = - resolvedBodyResult && resolvedBodyResult.resolvedSchemaType ? - result.resolvedSchemaType : - undefined; + resolvedSchemaTypeObject = resolvedBodyResult && resolvedBodyResult.resolvedSchemaType; resolvedBody && (bodyData = resolvedBody.request); @@ -1875,15 +1869,11 @@ let QUERYPARAM = 'query', else { resolvedBodyResult = resolveBodyData(context, requestContent[bodyType], bodyType); resolvedBody = - resolvedBodyResult && Array.isArray(resolvedBodyResult.generatedBody) && - resolvedBodyResult.generatedBody[0] ? - resolvedBodyResult.generatedBody[0] : - undefined; + resolvedBodyResult && + Array.isArray(resolvedBodyResult.generatedBody) && + resolvedBodyResult.generatedBody[0]; - resolvedSchemaTypeObject = - resolvedBodyResult && resolvedBodyResult.resolvedSchemaType ? - resolvedBodyResult.resolvedSchemaType : - undefined; + resolvedSchemaTypeObject = resolvedBodyResult && resolvedBodyResult.resolvedSchemaType; resolvedBody && (bodyData = resolvedBody.request); @@ -2074,18 +2064,14 @@ let QUERYPARAM = 'query', let queryParamTypeInfo = {}, properties = {}, - keyName, paramValue = resolveValueOfParameter(context, param); if (param && param.name && param.schema && param.schema.type) { - keyName = param.name; properties = createProperties(param); + queryParamTypeInfo = { keyName: param.name, properties }; + queryParamTypes.push(queryParamTypeInfo); } - queryParamTypeInfo = { keyName, properties }; - - queryParamTypes.push(queryParamTypeInfo); - if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, // which will be rejected by the collection v2 schema @@ -2105,8 +2091,8 @@ let QUERYPARAM = 'query', resolvePathParamsForPostmanRequest = (context, operationItem, method) => { const params = resolvePathItemParams(context, operationItem[method].parameters, operationItem.parameters), - pmParams = []; - let pathParamTypes = []; + pmParams = [], + pathParamTypes = []; _.forEach(params, (param) => { if (!_.isObject(param)) { @@ -2127,17 +2113,14 @@ let QUERYPARAM = 'query', let pathParamTypeInfo = {}, properties = {}, - keyName, paramValue = resolveValueOfParameter(context, param); if (param && param.name && param.schema && param.schema.type) { - keyName = param.name; properties = createProperties(param); + pathParamTypeInfo = { keyName: param.name, properties }; + pathParamTypes.push(pathParamTypeInfo); } - pathParamTypeInfo = { keyName, properties }; - pathParamTypes.push(pathParamTypeInfo); - if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, // which will be rejected by the collection v2 schema @@ -2211,18 +2194,14 @@ let QUERYPARAM = 'query', let headerTypeInfo = {}, properties = {}, - keyName, paramValue = resolveValueOfParameter(context, param); if (param && param.name && param.schema && param.schema.type) { - keyName = param.name; properties = createProperties(param); + headerTypeInfo = { keyName: param.name, properties }; + headerTypes.push(headerTypeInfo); } - headerTypeInfo = { keyName, properties }; - - headerTypes.push(headerTypeInfo); - if (typeof paramValue === 'number' || typeof paramValue === 'boolean') { // the SDK will keep the number-ness, // which will be rejected by the collection v2 schema @@ -2328,8 +2307,8 @@ let QUERYPARAM = 'query', resolveResponseHeaders = (context, responseHeaders) => { const headers = [], - { includeDeprecated } = context.computedOptions; - let headerTypes = []; + { includeDeprecated } = context.computedOptions, + headerTypes = []; if (_.has(responseHeaders, '$ref')) { responseHeaders = resolveSchema(context, responseHeaders, { isResponseSchema: true }); @@ -2346,8 +2325,7 @@ let QUERYPARAM = 'query', let headerValue = resolveValueOfParameter(context, value, { isResponseSchema: true }), headerTypeInfo = {}, - properties = {}, - keyName; + properties = {}; if (typeof headerValue === 'number' || typeof headerValue === 'boolean') { // the SDK will keep the number-ness, @@ -2363,8 +2341,7 @@ let QUERYPARAM = 'query', headers.push(...serialisedHeader); if (headerData && headerData.name && headerData.schema && headerData.schema.type) { - const { name, schema } = headerData; - keyName = name; + const { schema } = headerData; properties = { type: schema.type, format: schema.format, @@ -2379,10 +2356,9 @@ let QUERYPARAM = 'query', pattern: schema.pattern, example: schema.example }; - + headerTypeInfo = { keyName: headerData.name, properties }; + headerTypes.push(headerTypeInfo); } - headerTypeInfo = { keyName, properties }; - headerTypes.push(headerTypeInfo); }); return { resolvedHeaderTypes: headerTypes, headers }; @@ -2521,20 +2497,30 @@ let QUERYPARAM = 'query', } _.forOwn(operationItem.responses, (responseObj, code) => { - let responseSchema = _.has(responseObj, '$ref') ? ( - resolveSchema(context, responseObj, { isResponseSchema: true })) : responseObj, + let responseSchema = _.has(responseObj, '$ref') ? + resolveSchema(context, responseObj, { isResponseSchema: true }) : responseObj, { includeAuthInfoInExample } = context.computedOptions, auth = request.auth, resolvedExamples = resolveResponseBody(context, responseSchema, requestBodyExamples, code) || {}, { resolvedHeaderTypes, headers } = resolveResponseHeaders(context, responseSchema.headers), responseBodyHeaderObj; + + /* since resolvedExamples is a list of objects, we are picking the head element everytime + as the types are generated per example and even if we have response having same status code, + we resolve them all together */ + resolvedExamplesObject = resolvedExamples[0] && resolvedExamples[0].resolvedResponseBodyTypes; + responseBodyHeaderObj = { body: JSON.stringify(resolvedExamplesObject, null, 2), headers: JSON.stringify(resolvedHeaderTypes, null, 2) }; + // replace 'X' char in code with '0' | E.g. 5xx -> 500 + code = code.replace(/X|x/g, '0'); + code = code === 'default' ? 500 : _.toSafeInteger(code); + Object.assign(responseTypes, { [code]: responseBodyHeaderObj }); _.forOwn(resolvedExamples, (resolvedExample = {}) => { diff --git a/libV2/utils.js b/libV2/utils.js index 9d4df9a8..33375b77 100644 --- a/libV2/utils.js +++ b/libV2/utils.js @@ -28,10 +28,6 @@ const _ = require('lodash'), originalRequest.header = _.get(response, 'originalRequest.headers', []); originalRequest.body = requestItem.request.body; - // replace 'X' char with '0' - response.code = response.code.replace(/X|x/g, '0'); - response.code = response.code === 'default' ? 500 : _.toSafeInteger(response.code); - let sdkResponse = new Response({ name: response.name, code: response.code, diff --git a/test/unit/convertV2WithTypes.test.js b/test/unit/convertV2WithTypes.test.js index f4f629b3..a95c946a 100644 --- a/test/unit/convertV2WithTypes.test.js +++ b/test/unit/convertV2WithTypes.test.js @@ -2,7 +2,9 @@ // Disabling max Length for better visibility of the expectedExtractedTypes /* eslint-disable one-var */ -// Disabling as we want the checks to run in order of their declaration +/* Disabling as we want the checks to run in order of their declaration as declaring everything as once + even though initial declarations fails with test won't do any good */ + const expect = require('chai').expect, Converter = require('../../index.js'), @@ -49,7 +51,7 @@ const expect = require('chai').expect, }; -describe('convertV2WithTypes should generate collection confirming to collection schema', function() { +describe('convertV2WithTypes should generate collection conforming to collection schema', function() { it('Should generate collection conforming to schema for and fail if not valid ' + testSpec, function(done) { @@ -135,7 +137,7 @@ describe('convertV2WithTypes', function() { ); }); - it('should validate the schema' + testSpec1, function() { + it('should validate the generated type object' + testSpec1, function() { const example = { code: 200, message: 'Success' @@ -189,7 +191,6 @@ describe('convertV2WithTypes', function() { const element = Object.values(conversionResult.extractedTypes)[0]; const { response } = element; - // Get the schema from the response const [key, value] = Object.entries(response)[0]; expect(value).to.have.property('body').that.is.a('string'); @@ -216,7 +217,7 @@ describe('convertV2WithTypes', function() { 'body': '{\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "integer",\n "format": "int64"\n },\n "name": {\n "type": "string"\n },\n "tag": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "name"\n ]\n }\n}', 'headers': '[\n {\n "keyName": "x-next",\n "properties": {\n "type": "string",\n "default": "",\n "required": false,\n "deprecated": false\n }\n }\n]' }, - 'default': { + '500': { 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', 'headers': '[]' } @@ -232,9 +233,9 @@ describe('convertV2WithTypes', function() { '201': { 'headers': '[]' }, - 'default': { + '500': { 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', - 'headers': '[\n {\n "properties": {}\n }\n]' + 'headers': '[]' } } }, @@ -249,7 +250,7 @@ describe('convertV2WithTypes', function() { 'body': '{\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "integer",\n "format": "int64"\n },\n "name": {\n "type": "string"\n },\n "tag": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "name"\n ]\n }\n}', 'headers': '[]' }, - 'default': { + '500': { 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', 'headers': '[]' } @@ -266,7 +267,7 @@ describe('convertV2WithTypes', function() { 'body': '{\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "integer",\n "format": "int64"\n },\n "name": {\n "type": "string"\n },\n "tag": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "name"\n ]\n }\n}', 'headers': '[]' }, - 'default': { + '500': { 'body': '{\n "type": "object",\n "properties": {\n "code": {\n "type": "integer"\n },\n "message": {\n "type": "string"\n }\n },\n "required": [\n "code",\n "message"\n ]\n}', 'headers': '[]' } From 415f18615caef1c4065e6b088f3ef43f108e6fbd Mon Sep 17 00:00:00 2001 From: Ayush Shrivastav Date: Wed, 8 Jan 2025 17:23:42 +0530 Subject: [PATCH 19/19] AB-289-resolved-comments --- libV2/schemaUtils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libV2/schemaUtils.js b/libV2/schemaUtils.js index 45b6ec1d..f60fe244 100644 --- a/libV2/schemaUtils.js +++ b/libV2/schemaUtils.js @@ -2168,8 +2168,8 @@ let QUERYPARAM = 'query', resolveHeadersForPostmanRequest = (context, operationItem, method) => { const params = resolvePathItemParams(context, operationItem[method].parameters, operationItem.parameters), pmParams = [], + headerTypes = [], { keepImplicitHeaders, includeDeprecated } = context.computedOptions; - let headerTypes = []; _.forEach(params, (param) => { if (!_.isObject(param)) { @@ -2506,8 +2506,8 @@ let QUERYPARAM = 'query', responseBodyHeaderObj; /* since resolvedExamples is a list of objects, we are picking the head element everytime - as the types are generated per example and even if we have response having same status code, - we resolve them all together */ + as the types are generated per example and since we have response having same status code, + so their type would be also same */ resolvedExamplesObject = resolvedExamples[0] && resolvedExamples[0].resolvedResponseBodyTypes;