Skip to content

Commit

Permalink
feat(resolver): add support for OpenAPI 3.1.0 resolution
Browse files Browse the repository at this point in the history
Refs #2744
  • Loading branch information
char0n committed Jan 4, 2023
1 parent 0dff166 commit a9e0283
Show file tree
Hide file tree
Showing 8 changed files with 627 additions and 86 deletions.
158 changes: 79 additions & 79 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@
},
"dependencies": {
"@babel/runtime-corejs3": "^7.11.2",
"@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",
"@swagger-api/apidom-core": "^0.62.1",
"@swagger-api/apidom-reference": "^0.62.1",
"@swagger-api/apidom-ns-openapi-3-1": "^0.62.1",
"@swagger-api/apidom-json-pointer": "^0.62.1",
"cookie": "~0.5.0",
"cross-fetch": "^3.1.5",
"deepmerge": "~4.2.2",
Expand Down
7 changes: 6 additions & 1 deletion src/resolver/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// eslint-disable-next-line camelcase
import resolveOpenAPI2_30Strategy from './strategies/openapi-2--3-0.js';
import resolveOpenAPI31Strategy from './strategies/openapi-3-1.js';
import { makeFetchJSON } from './utils/index.js';
import * as optionsUtil from './utils/options.js';
import { isOpenAPI31 } from '../helpers/openapi-predicates.js';

const resolve = async (options) => {
const { spec, requestInterceptor, responseInterceptor } = options;
Expand All @@ -11,8 +13,11 @@ const resolve = async (options) => {
const retrievedSpec =
spec ||
(await makeFetchJSON(httpClient, { requestInterceptor, responseInterceptor })(retrievalURI));
const strategyOptions = { ...options, spec: retrievedSpec };

return resolveOpenAPI2_30Strategy({ ...options, spec: retrievedSpec });
return isOpenAPI31(retrievedSpec)
? resolveOpenAPI31Strategy(strategyOptions)
: resolveOpenAPI2_30Strategy(strategyOptions);
};

export default resolve;
67 changes: 67 additions & 0 deletions src/resolver/strategies/openapi-3-1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* eslint-disable camelcase */
import { toValue } from '@swagger-api/apidom-core';
import { OpenApi3_1Element } from '@swagger-api/apidom-ns-openapi-3-1';
import { dereferenceApiDOM } from '@swagger-api/apidom-reference/configuration/empty';
import BinaryParser from '@swagger-api/apidom-reference/parse/parsers/binary';
import OpenApi3_1ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1';

import * as optionsUtil from '../utils/options.js';
import normalizeOpenAPI31 from '../../helpers/normalize/openapi-3-1.js';
import HttpResolverSwaggerClient from '../../helpers/apidom/reference/resolve/resolvers/http-swagger-client/index.js';
import JsonParser from '../../helpers/apidom/reference/parse/parsers/json/index.js';
import YamlParser from '../../helpers/apidom/reference/parse/parsers/yaml-1-2/index.js';
import OpenApiJson3_1Parser from '../../helpers/apidom/reference/parse/parsers/openapi-json-3-1/index.js';
import OpenApiYaml3_1Parser from '../../helpers/apidom/reference/parse/parsers/openapi-yaml-3-1/index.js';
import OpenApi3_1SwaggerClientDereferenceStrategy from '../../helpers/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js';

const resolveOpenAPI31Strategy = async (options) => {
const {
spec,
timeout,
redirects,
requestInterceptor,
responseInterceptor,
allowMetaPatches = false,
useCircularStructures = false,
skipNormalization = false,
} = options;
const openApiElement = OpenApi3_1Element.refract(spec);
const dereferenced = await dereferenceApiDOM(openApiElement, {
resolve: {
baseURI: optionsUtil.retrievalURI(options),
resolvers: [
HttpResolverSwaggerClient({
timeout: timeout || 10000,
redirects: redirects || 10,
}),
],
resolverOpts: {
swaggerHTTPClientConfig: {
requestInterceptor,
responseInterceptor,
},
},
strategies: [OpenApi3_1ResolveStrategy()],
},
parse: {
parsers: [
OpenApiJson3_1Parser({ allowEmpty: false, sourceMap: false }),
OpenApiYaml3_1Parser({ allowEmpty: false, sourceMap: false }),
JsonParser({ allowEmpty: false, sourceMap: false }),
YamlParser({ allowEmpty: false, sourceMap: false }),
BinaryParser({ allowEmpty: false, sourceMap: false }),
],
},
dereference: {
strategies: [
OpenApi3_1SwaggerClientDereferenceStrategy({ allowMetaPatches, useCircularStructures }),
],
},
});
const normalized = skipNormalization ? dereferenced : normalizeOpenAPI31(dereferenced);

return { spec: toValue(normalized), errors: [] };
};

export default resolveOpenAPI31Strategy;
/* eslint-enable camelcase */
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';
import fs from 'fs';
import jsYaml from 'js-yaml';

import Swagger from '../../src/index.js';
import Swagger from '../../../src/index.js';

describe('resolver', () => {
afterEach(() => {
Expand Down Expand Up @@ -721,7 +721,7 @@ describe('resolver', () => {
test('should not throw errors on resvered-keywords in freely-named-fields', () => {
// Given
const ReservedKeywordSpec = jsYaml.load(
fs.readFileSync(path.resolve(__dirname, '../data/reserved-keywords.yaml'), 'utf8')
fs.readFileSync(path.resolve(__dirname, '../../data/reserved-keywords.yaml'), 'utf8')
);

// When
Expand Down
179 changes: 179 additions & 0 deletions test/resolver/strategies/openapi-3-1/__fixtures__/petstore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{
"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"
}
}
}
}
}
}
},
"/pets/{petId}": {
"get": {
"summary": "Info for a specific pet",
"operationId": "showPetById",
"tags": [
"pets"
],
"parameters": [
{
"name": "petId",
"in": "path",
"required": true,
"description": "The id of the pet to retrieve",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Expected response to a valid request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
"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"
}
}
}
}
}
}
Loading

0 comments on commit a9e0283

Please sign in to comment.