diff --git a/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js b/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js index ba2816801..6c6f73a08 100644 --- a/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js +++ b/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js @@ -12,14 +12,17 @@ const OpenApi3_1SwaggerClientDereferenceStrategy = OpenApi3_1DereferenceStrategy props: { useCircularStructures: true, allowMetaPatches: false, + parameterMacro: null, }, init({ useCircularStructures = this.useCircularStructures, allowMetaPatches = this.allowMetaPatches, + parameterMacro = this.parameterMacro, } = {}) { this.name = 'openapi-3-1-swagger-client'; this.useCircularStructures = useCircularStructures; this.allowMetaPatches = allowMetaPatches; + this.parameterMacro = parameterMacro; }, methods: { async dereference(file, options) { @@ -41,6 +44,7 @@ const OpenApi3_1SwaggerClientDereferenceStrategy = OpenApi3_1DereferenceStrategy options, useCircularStructures: this.useCircularStructures, allowMetaPatches: this.allowMetaPatches, + parameterMacro: this.parameterMacro, }); const dereferencedElement = await visitAsync(refSet.rootRef.value, visitor, { keyMap, diff --git a/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitor.js b/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitor.js index 86376cd6b..ec34a4169 100644 --- a/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitor.js +++ b/src/helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/visitor.js @@ -4,6 +4,7 @@ import { isPrimitiveElement, isStringElement, visit, + toValue, includesClasses, } from '@swagger-api/apidom-core'; import { @@ -40,17 +41,28 @@ import { const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')]; -const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.compose({ - props: { - useCircularStructures: true, - allowMetaPatches: false, - }, - init({ useCircularStructures, allowMetaPatches }) { +const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.init( + function _OpenApi3_1SwaggerClientDereferenceVisitor({ + useCircularStructures = true, + allowMetaPatches = false, + parameterMacro = null, + }) { + const instance = this; + let parameterMacroOperation = null; + + // props this.useCircularStructures = useCircularStructures; this.allowMetaPatches = allowMetaPatches; - }, - methods: { - async ReferenceElement(referenceElement, key, parent, path, ancestors) { + this.parameterMacro = parameterMacro; + + // methods + this.ReferenceElement = async function _ReferenceElement( + referenceElement, + key, + parent, + path, + ancestors + ) { const [ancestorsLineage, directAncestors] = this.toAncestorLineage(ancestors); // skip already identified cycled Path Item Objects @@ -119,6 +131,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c ancestors: ancestorsLineage, allowMetaPatches: this.allowMetaPatches, useCircularStructures: this.useCircularStructures, + parameterMacro: this.parameterMacro, }); fragment = await visitAsync(fragment, visitor, { keyMap, nodeTypeGetter: getNodeType }); @@ -176,9 +189,15 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c // transclude the element for a fragment return fragment; - }, - - async PathItemElement(pathItemElement, key, parent, path, ancestors) { + }; + + this.PathItemElement = async function _PathItemElement( + pathItemElement, + key, + parent, + path, + ancestors + ) { const [ancestorsLineage, directAncestors] = this.toAncestorLineage(ancestors); // ignore PathItemElement without $ref field @@ -242,6 +261,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c ancestors: ancestorsLineage, allowMetaPatches: this.allowMetaPatches, useCircularStructures: this.useCircularStructures, + parameterMacro: this.parameterMacro, }); referencedElement = await visitAsync(referencedElement, visitor, { keyMap, @@ -302,9 +322,15 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c // transclude referencing element with merged referenced element return mergedPathItemElement; - }, - - async SchemaElement(referencingElement, key, parent, path, ancestors) { + }; + + this.SchemaElement = async function _SchemaElement( + referencingElement, + key, + parent, + path, + ancestors + ) { const [ancestorsLineage, directAncestors] = this.toAncestorLineage(ancestors); // skip current referencing schema as $ref keyword was not defined @@ -414,6 +440,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c options: this.options, useCircularStructures: this.useCircularStructures, allowMetaPatches: this.allowMetaPatches, + parameterMacro: this.parameterMacro, ancestors: ancestorsLineage, }); referencedElement = await visitAsync(referencedElement, mergeVisitor, { @@ -490,9 +517,31 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c // transclude referencing element with merged referenced element return mergedSchemaElement; - }, - }, -}); + }; + + this.OperationElement = { + enter(operationElement) { + parameterMacroOperation = operationElement; + }, + leave() { + parameterMacroOperation = null; + }, + }; + + this.ParameterElement = { + leave(parameterElement) { + if (typeof instance.parameterMacro !== 'function') return; + + const pojoOperation = + parameterMacroOperation === null ? null : toValue(parameterMacroOperation); + const pojoParameter = toValue(parameterElement); + const defaultValue = instance.parameterMacro(pojoOperation, pojoParameter); + + parameterElement.set('default', defaultValue); + }, + }; + } +); export default OpenApi3_1SwaggerClientDereferenceVisitor; /* eslint-enable camelcase */ diff --git a/src/resolver/strategies/openapi-3-1.js b/src/resolver/strategies/openapi-3-1.js index 8f8fbdda9..40747f83d 100644 --- a/src/resolver/strategies/openapi-3-1.js +++ b/src/resolver/strategies/openapi-3-1.js @@ -34,6 +34,7 @@ const resolveOpenAPI31Strategy = async (options) => { allowMetaPatches = false, useCircularStructures = false, skipNormalization = false, + parameterMacro = null, } = options; // determining BaseURI const defaultBaseURI = 'https://smartbear.com/'; @@ -90,7 +91,11 @@ const resolveOpenAPI31Strategy = async (options) => { }, dereference: { strategies: [ - OpenApi3_1SwaggerClientDereferenceStrategy({ allowMetaPatches, useCircularStructures }), + OpenApi3_1SwaggerClientDereferenceStrategy({ + allowMetaPatches, + useCircularStructures, + parameterMacro, + }), ], refSet, }, diff --git a/test/resolver/strategies/openapi-3-1/__fixtures__/parameter-macro-no-operation.json b/test/resolver/strategies/openapi-3-1/__fixtures__/parameter-macro-no-operation.json new file mode 100644 index 000000000..845d00137 --- /dev/null +++ b/test/resolver/strategies/openapi-3-1/__fixtures__/parameter-macro-no-operation.json @@ -0,0 +1,25 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "components": { + "parameters": { + "limit": { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "format": "int32" + } + } + } + } +} diff --git a/test/resolver/strategies/openapi-3-1/__fixtures__/parameter-macro.json b/test/resolver/strategies/openapi-3-1/__fixtures__/parameter-macro.json new file mode 100644 index 000000000..1e3745765 --- /dev/null +++ b/test/resolver/strategies/openapi-3-1/__fixtures__/parameter-macro.json @@ -0,0 +1,137 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1" + } + ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "A paged array of pets", + "headers": { + "x-next": { + "description": "A link to the next page of responses", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pets" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "tags": [ + "pets" + ], + "responses": { + "201": { + "description": "Null response" + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Pets": { + "type": "array", + "maxItems": 100, + "items": { + "$ref": "#/components/schemas/Pet" + } + }, + "Error": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} diff --git a/test/resolver/strategies/openapi-3-1/__snapshots__/index.js.snap b/test/resolver/strategies/openapi-3-1/__snapshots__/index.js.snap index 8ada9a4ec..b350636af 100644 --- a/test/resolver/strategies/openapi-3-1/__snapshots__/index.js.snap +++ b/test/resolver/strategies/openapi-3-1/__snapshots__/index.js.snap @@ -1594,6 +1594,261 @@ exports[`resolve OpenAPI 3.1.0 strategy given OpenAPI 3.1.0 definition via spec } `; +exports[`resolve OpenAPI 3.1.0 strategy given OpenAPI 3.1.0 definition via spec option and parameterMacro is provided sa a function should call parameterMacro with Operation and Parameter Objects 1`] = ` +{ + "errors": [], + "spec": { + "$$normalized": true, + "components": { + "schemas": { + "Error": { + "properties": { + "code": { + "format": "int32", + "type": "integer", + }, + "message": { + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + "Pet": { + "properties": { + "id": { + "format": "int64", + "type": "integer", + }, + "name": { + "type": "string", + }, + "tag": { + "type": "string", + }, + }, + "required": [ + "id", + "name", + ], + "type": "object", + }, + "Pets": { + "items": { + "properties": { + "id": { + "format": "int64", + "type": "integer", + }, + "name": { + "type": "string", + }, + "tag": { + "type": "string", + }, + }, + "required": [ + "id", + "name", + ], + "type": "object", + }, + "maxItems": 100, + "type": "array", + }, + }, + }, + "info": { + "license": { + "name": "MIT", + }, + "title": "Swagger Petstore", + "version": "1.0.0", + }, + "openapi": "3.1.0", + "paths": { + "/pets": { + "get": { + "operationId": "listPets", + "parameters": [ + { + "default": "listPets-limit", + "description": "How many items to return at one time (max 100)", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "int32", + "maximum": 100, + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "properties": { + "id": { + "format": "int64", + "type": "integer", + }, + "name": { + "type": "string", + }, + "tag": { + "type": "string", + }, + }, + "required": [ + "id", + "name", + ], + "type": "object", + }, + "maxItems": 100, + "type": "array", + }, + }, + }, + "description": "A paged array of pets", + "headers": { + "x-next": { + "description": "A link to the next page of responses", + "schema": { + "type": "string", + }, + }, + }, + }, + "default": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "format": "int32", + "type": "integer", + }, + "message": { + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, + "description": "unexpected error", + }, + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1", + }, + ], + "summary": "List all pets", + "tags": [ + "pets", + ], + }, + "post": { + "operationId": "createPets", + "responses": { + "201": { + "description": "Null response", + }, + "default": { + "content": { + "application/json": { + "schema": { + "properties": { + "code": { + "format": "int32", + "type": "integer", + }, + "message": { + "type": "string", + }, + }, + "required": [ + "code", + "message", + ], + "type": "object", + }, + }, + }, + "description": "unexpected error", + }, + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1", + }, + ], + "summary": "Create a pet", + "tags": [ + "pets", + ], + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1", + }, + ], + }, + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1", + }, + ], + }, +} +`; + +exports[`resolve OpenAPI 3.1.0 strategy given OpenAPI 3.1.0 definition via spec option and parameterMacro is provided sa a function should call parameterMacro with Parameter Object only 1`] = ` +{ + "errors": [], + "spec": { + "$$normalized": true, + "components": { + "parameters": { + "limit": { + "default": "null-limit", + "description": "How many items to return at one time (max 100)", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "int32", + "maximum": 100, + "type": "integer", + }, + }, + }, + }, + "info": { + "license": { + "name": "MIT", + }, + "title": "Swagger Petstore", + "version": "1.0.0", + }, + "openapi": "3.1.0", + }, +} +`; + exports[`resolve OpenAPI 3.1.0 strategy given OpenAPI 3.1.0 definition via spec option and pathDiscriminator is empty list should resolve entire spec 1`] = ` { "errors": [], diff --git a/test/resolver/strategies/openapi-3-1/index.js b/test/resolver/strategies/openapi-3-1/index.js index aaea4ae0b..29e4a9aa5 100644 --- a/test/resolver/strategies/openapi-3-1/index.js +++ b/test/resolver/strategies/openapi-3-1/index.js @@ -194,6 +194,30 @@ describe('resolve', () => { await expect(resolveThunk()).rejects.toThrow(EvaluationJsonPointerError); }); }); + + describe('and parameterMacro is provided sa a function', () => { + test('should call parameterMacro with Operation and Parameter Objects', async () => { + const spec = globalThis.loadJsonFile(path.join(fixturePath, 'parameter-macro.json')); + const resolvedSpec = await SwaggerClient.resolve({ + spec, + parameterMacro: (operation, parameter) => `${operation.operationId}-${parameter.name}`, + }); + + expect(resolvedSpec).toMatchSnapshot(); + }); + + test('should call parameterMacro with Parameter Object only', async () => { + const spec = globalThis.loadJsonFile( + path.join(fixturePath, 'parameter-macro-no-operation.json') + ); + const resolvedSpec = await SwaggerClient.resolve({ + spec, + parameterMacro: (operation, parameter) => `${String(operation)}-${parameter.name}`, + }); + + expect(resolvedSpec).toMatchSnapshot(); + }); + }); }); }); });