diff --git a/package.json b/package.json index 478a622670..4605df2a30 100644 --- a/package.json +++ b/package.json @@ -111,10 +111,10 @@ }, "dependencies": { "@babel/runtime-corejs3": "^7.11.2", - "@swagger-api/apidom-core": "^0.61.0", - "@swagger-api/apidom-reference": "^0.61.0", - "@swagger-api/apidom-ns-openapi-3-1": "^0.61.0", - "@swagger-api/apidom-json-pointer": "^0.61.0", + "@swagger-api/apidom-core": "^0.62.0", + "@swagger-api/apidom-reference": "^0.62.0", + "@swagger-api/apidom-ns-openapi-3-1": "^0.62.0", + "@swagger-api/apidom-json-pointer": "^0.62.0", "cookie": "~0.5.0", "cross-fetch": "^3.1.5", "deepmerge": "~4.2.2", diff --git a/src/helpers/normalize/openapi-3-1.js b/src/helpers/normalize/openapi-3-1.js index e69de29bb2..dde9e01697 100644 --- a/src/helpers/normalize/openapi-3-1.js +++ b/src/helpers/normalize/openapi-3-1.js @@ -0,0 +1,41 @@ +import { dispatchRefractorPlugins, isObjectElement } from '@swagger-api/apidom-core'; +import { + refractorPluginNormalizeOperationIds, + refractorPluginNormalizeParameters, + refractorPluginNormalizeSecurityRequirements, + refractorPluginNormalizeServers, + refractorPluginNormalizeParameterExamples, + refractorPluginNormalizeHeaderExamples, + createToolbox, + keyMap, + getNodeType, +} from '@swagger-api/apidom-ns-openapi-3-1'; + +import opId from '../op-id.js'; + +const normalize = (element) => { + if (!isObjectElement(element)) return element; + if (element.hasKey('$$normalized')) return element; + + const plugins = [ + refractorPluginNormalizeOperationIds({ + operationIdNormalizer: (operationId, path, method) => + opId({ operationId }, path, method, { v2OperationIdCompatibilityMode: false }), + }), + refractorPluginNormalizeParameters(), + refractorPluginNormalizeSecurityRequirements(), + refractorPluginNormalizeServers(), + refractorPluginNormalizeParameterExamples(), + refractorPluginNormalizeHeaderExamples(), + ]; + + const normalized = dispatchRefractorPlugins(element, plugins, { + toolboxCreator: createToolbox, + visitorOptions: { keyMap, nodeTypeGetter: getNodeType }, + }); + + normalized.set('$$normalized', true); + return normalized; +}; + +export default normalize; diff --git a/test/helpers/normalize/openapi-3-1/__fixtures__/header-examples.json b/test/helpers/normalize/openapi-3-1/__fixtures__/header-examples.json new file mode 100644 index 0000000000..beaf992270 --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__fixtures__/header-examples.json @@ -0,0 +1,28 @@ +{ + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "responses": { + "200": { + "headers": { + "content-type": { + "schema": { + "type": "number", + "examples": [ + 1 + ] + }, + "examples": { + "example1": { + "value": 2 + } + } + } + } + } + } + } + } + } +} diff --git a/test/helpers/normalize/openapi-3-1/__fixtures__/operation-ids.json b/test/helpers/normalize/openapi-3-1/__fixtures__/operation-ids.json new file mode 100644 index 0000000000..13e8da8093 --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__fixtures__/operation-ids.json @@ -0,0 +1,10 @@ +{ + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "operationId": "get operation ^" + } + } + } +} diff --git a/test/helpers/normalize/openapi-3-1/__fixtures__/parameter-examples.json b/test/helpers/normalize/openapi-3-1/__fixtures__/parameter-examples.json new file mode 100644 index 0000000000..7f72e697b8 --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__fixtures__/parameter-examples.json @@ -0,0 +1,24 @@ +{ + "openapi": "3.1.0", + "paths": { + "/": { + "parameters": [ + { + "name": "param1", + "in": "query", + "schema": { + "type": "number", + "examples": [ + 1 + ] + }, + "examples": { + "example1": { + "value": 2 + } + } + } + ] + } + } +} diff --git a/test/helpers/normalize/openapi-3-1/__fixtures__/parameters.json b/test/helpers/normalize/openapi-3-1/__fixtures__/parameters.json new file mode 100644 index 0000000000..33d5f404f9 --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__fixtures__/parameters.json @@ -0,0 +1,18 @@ +{ + "openapi": "3.1.0", + "paths": { + "/": { + "parameters": [ + { + "name": "param1", + "in": "query" + }, + { + "name": "param2", + "in": "query" + } + ], + "get": {} + } + } +} diff --git a/test/helpers/normalize/openapi-3-1/__fixtures__/security-requirements.json b/test/helpers/normalize/openapi-3-1/__fixtures__/security-requirements.json new file mode 100644 index 0000000000..9322eefbdd --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__fixtures__/security-requirements.json @@ -0,0 +1,16 @@ +{ + "openapi": "3.1.0", + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "paths": { + "/": { + "get": {} + } + } +} diff --git a/test/helpers/normalize/openapi-3-1/__fixtures__/servers.json b/test/helpers/normalize/openapi-3-1/__fixtures__/servers.json new file mode 100644 index 0000000000..e2ba276f87 --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__fixtures__/servers.json @@ -0,0 +1,14 @@ +{ + "openapi": "3.1.0", + "servers": [ + { + "url": "https://example.com/", + "description": "production server" + } + ], + "paths": { + "/": { + "get": {} + } + } +} diff --git a/test/helpers/normalize/openapi-3-1/__snapshots__/index.js.snap b/test/helpers/normalize/openapi-3-1/__snapshots__/index.js.snap new file mode 100644 index 0000000000..af89d643f6 --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/__snapshots__/index.js.snap @@ -0,0 +1,169 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`helpers normalize OpenAPI 3.1 given denormalized Header Object examples should normalize 1`] = ` +{ + "$$normalized": true, + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "responses": { + "200": { + "headers": { + "content-type": { + "examples": { + "example1": { + "value": 2, + }, + }, + "schema": { + "examples": [ + 2, + ], + "type": "number", + }, + }, + }, + }, + }, + }, + }, + }, +} +`; + +exports[`helpers normalize OpenAPI 3.1 given denormalized Operation.id fields should normalize 1`] = ` +{ + "$$normalized": true, + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "__originalOperationId": "get operation ^", + "operationId": "get_operation__", + }, + }, + }, +} +`; + +exports[`helpers normalize OpenAPI 3.1 given denormalized Parameter Object examples should normalize 1`] = ` +{ + "$$normalized": true, + "openapi": "3.1.0", + "paths": { + "/": { + "parameters": [ + { + "examples": { + "example1": { + "value": 2, + }, + }, + "in": "query", + "name": "param1", + "schema": { + "examples": [ + 2, + ], + "type": "number", + }, + }, + ], + }, + }, +} +`; + +exports[`helpers normalize OpenAPI 3.1 given denormalized Parameter Objects should normalize 1`] = ` +{ + "$$normalized": true, + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "parameters": [ + { + "in": "query", + "name": "param1", + }, + { + "in": "query", + "name": "param2", + }, + ], + }, + "parameters": [ + { + "in": "query", + "name": "param1", + }, + { + "in": "query", + "name": "param2", + }, + ], + }, + }, +} +`; + +exports[`helpers normalize OpenAPI 3.1 given denormalized Security Requirements Objects should normalize 1`] = ` +{ + "$$normalized": true, + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets", + ], + }, + ], + }, + }, + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets", + ], + }, + ], +} +`; + +exports[`helpers normalize OpenAPI 3.1 given denormalized Servers Objects should normalize 1`] = ` +{ + "$$normalized": true, + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "servers": [ + { + "description": "production server", + "url": "https://example.com/", + }, + ], + }, + "servers": [ + { + "description": "production server", + "url": "https://example.com/", + }, + ], + }, + }, + "servers": [ + { + "description": "production server", + "url": "https://example.com/", + }, + ], +} +`; diff --git a/test/helpers/normalize/openapi-3-1/index.js b/test/helpers/normalize/openapi-3-1/index.js new file mode 100644 index 0000000000..31e36ea81d --- /dev/null +++ b/test/helpers/normalize/openapi-3-1/index.js @@ -0,0 +1,103 @@ +import path from 'node:path'; +/* eslint-disable camelcase */ +import { toValue, StringElement } from '@swagger-api/apidom-core'; +import { OpenApi3_1Element } from '@swagger-api/apidom-ns-openapi-3-1'; + +import normalizeOpenAPI31 from '../../../../src/helpers/normalize/openapi-3-1.js'; + +const fixturesPath = path.join(__dirname, '__fixtures__'); + +/** + * This test suite only guarantees that normalization works for OpenAPI 3.1.0. + * Complete coverage of all use-cases including corner-cases are covered + * upstream in ApiDOM. + */ + +describe('helpers', () => { + describe('normalize', () => { + describe('OpenAPI 3.1', () => { + describe('given denormalized Header Object examples', () => { + test('should normalize', () => { + const spec = globalThis.loadJsonFile(path.join(fixturesPath, 'header-examples.json')); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + + expect(toValue(normalized)).toMatchSnapshot(); + }); + }); + + describe('given denormalized Parameter Object examples', () => { + test('should normalize', () => { + const spec = globalThis.loadJsonFile(path.join(fixturesPath, 'parameter-examples.json')); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + + expect(toValue(normalized)).toMatchSnapshot(); + }); + }); + + describe('given denormalized Operation.id fields', () => { + test('should normalize', () => { + const spec = globalThis.loadJsonFile(path.join(fixturesPath, 'operation-ids.json')); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + + expect(toValue(normalized)).toMatchSnapshot(); + }); + }); + + describe('given denormalized Parameter Objects', () => { + test('should normalize', () => { + const spec = globalThis.loadJsonFile(path.join(fixturesPath, 'parameters.json')); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + + expect(toValue(normalized)).toMatchSnapshot(); + }); + }); + + describe('given denormalized Security Requirements Objects', () => { + test('should normalize', () => { + const spec = globalThis.loadJsonFile( + path.join(fixturesPath, 'security-requirements.json') + ); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + + expect(toValue(normalized)).toMatchSnapshot(); + }); + }); + + describe('given denormalized Servers Objects', () => { + test('should normalize', () => { + const spec = globalThis.loadJsonFile(path.join(fixturesPath, 'servers.json')); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + + expect(toValue(normalized)).toMatchSnapshot(); + }); + }); + + describe('given element non compatible with ObjectElement', () => { + test('should skip normalization', () => { + const element = new StringElement('test'); + const normalized = normalizeOpenAPI31(element); + + expect(normalized).toEqual(element); + }); + }); + + describe('given spec is already normalized', () => { + test('should skip normalization', () => { + const spec = globalThis.loadJsonFile(path.join(fixturesPath, 'servers.json')); + const openApiElement = OpenApi3_1Element.refract(spec); + const normalized = normalizeOpenAPI31(openApiElement); + const doubleNormalized = normalizeOpenAPI31(openApiElement); + + expect(normalized).toEqual(doubleNormalized); + }); + }); + }); + }); +}); +/* eslint-enable camelcase */