Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(resolver): don't skip normalization of OpenAPI 3.1.0 specs #3575

Merged
merged 6 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime-corejs3": "^7.22.15",
"@swagger-api/apidom-core": ">=1.0.0-alpha.5 <1.0.0-beta.0",
"@swagger-api/apidom-error": ">=1.0.0-alpha.5 <1.0.0-beta.0",
"@swagger-api/apidom-json-pointer": ">=1.0.0-alpha.5 <1.0.0-beta.0",
"@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-alpha.5 <1.0.0-beta.0",
"@swagger-api/apidom-reference": ">=1.0.0-alpha.5 <1.0.0-beta.0",
"@swagger-api/apidom-core": ">=1.0.0-alpha.6 <1.0.0-beta.0",
"@swagger-api/apidom-error": ">=1.0.0-alpha.6 <1.0.0-beta.0",
"@swagger-api/apidom-json-pointer": ">=1.0.0-alpha.6 <1.0.0-beta.0",
"@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-alpha.6 <1.0.0-beta.0",
"@swagger-api/apidom-reference": ">=1.0.0-alpha.6 <1.0.0-beta.0",
"cookie": "~0.6.0",
"deepmerge": "~4.3.0",
"fast-json-patch": "^3.0.0-1",
Expand Down
2 changes: 1 addition & 1 deletion src/resolver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const resolve = async (options) => {
spec ||
(await makeFetchJSON(httpClient, { requestInterceptor, responseInterceptor })(retrievalURI));
const strategyOptions = { ...options, spec: retrievedSpec };
const strategy = options.strategies.find((strg) => strg.match(strategyOptions));
const strategy = options.strategies.find((strg) => strg.match(retrievedSpec));

return strategy.resolve(strategyOptions);
};
Expand Down
2 changes: 1 addition & 1 deletion src/resolver/strategies/generic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const genericStrategy = {
match() {
return true;
},
normalize({ spec }) {
normalize(spec) {
const { spec: normalized } = normalize({ spec });
return normalized;
},
Expand Down
18 changes: 12 additions & 6 deletions src/resolver/strategies/generic/resolve.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import mapSpec, { plugins } from '../../specmap/index.js';
import normalize from './normalize.js';
import { makeFetchJSON } from '../../utils/index.js';
import * as optionsUtil from '../../utils/options.js';

Expand All @@ -13,16 +12,18 @@ export default async function resolveGenericStrategy(options) {
parameterMacro,
requestInterceptor,
responseInterceptor,
skipNormalization,
skipNormalization = false,
useCircularStructures,
strategies,
} = options;

const retrievalURI = optionsUtil.retrievalURI(options);
const httpClient = optionsUtil.httpClient(options);
const strategy = strategies.find((strg) => strg.match(spec));

return doResolve(spec);

function doResolve(_spec) {
async function doResolve(_spec) {
if (retrievalURI) {
plugins.refs.docCache[retrievalURI] = _spec;
}
Expand All @@ -45,7 +46,7 @@ export default async function resolveGenericStrategy(options) {
}

// mapSpec is where the hard work happens
return mapSpec({
const result = await mapSpec({
spec: _spec,
context: { baseDoc: retrievalURI },
plugins: plugs,
Expand All @@ -54,7 +55,12 @@ export default async function resolveGenericStrategy(options) {
parameterMacro,
modelPropertyMacro,
useCircularStructures,
// eslint-disable-next-line camelcase
}).then(skipNormalization ? async (a) => a : normalize);
});

if (!skipNormalization) {
result.spec = strategy.normalize(result.spec);
}

return result;
}
}
4 changes: 2 additions & 2 deletions src/resolver/strategies/openapi-2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export { clearCache } from '../generic/index.js';

const openApi2Strategy = {
name: 'openapi-2',
match({ spec }) {
match(spec) {
return isOpenAPI2(spec);
},
normalize({ spec }) {
normalize(spec) {
const { spec: normalized } = normalize({ spec });
return normalized;
},
Expand Down
4 changes: 2 additions & 2 deletions src/resolver/strategies/openapi-3-0/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export { clearCache } from '../generic/index.js';

const openApi30Strategy = {
name: 'openapi-3-0',
match({ spec }) {
match(spec) {
return isOpenAPI30(spec);
},
normalize({ spec }) {
normalize(spec) {
const { spec: normalized } = normalize({ spec });
return normalized;
},
Expand Down
20 changes: 17 additions & 3 deletions src/resolver/strategies/openapi-3-1-apidom/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { isPlainObject } from 'ramda-adjunct';
import { isElement } from '@swagger-api/apidom-core';

import resolveOpenAPI31Strategy from './resolve.js';
import normalize, { pojoAdapter } from './normalize.js';
import { isOpenAPI31 } from '../../../helpers/openapi-predicates.js';

const openApi31ApiDOMStrategy = {
name: 'openapi-3-1-apidom',
match({ spec }) {
match(spec) {
return isOpenAPI31(spec);
},
normalize({ spec }) {
return pojoAdapter(normalize)(spec);
normalize(spec) {
// pre-normalization - happens only once before the first lazy dereferencing and in JavaScript context
if (!isElement(spec) && isPlainObject(spec) && !spec.$$normalized) {
const preNormalized = pojoAdapter(normalize)(spec);
preNormalized.$$normalized = true;
return preNormalized;
}
// post-normalization - happens after each dereferencing and in ApiDOM context
if (isElement(spec)) {
return normalize(spec);
}

return spec;
},
async resolve(options) {
return resolveOpenAPI31Strategy(options);
Expand Down
17 changes: 9 additions & 8 deletions src/resolver/strategies/openapi-3-1-apidom/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {
} from '@swagger-api/apidom-ns-openapi-3-1';

import opId from '../../../helpers/op-id.js';
import resolveOpenAPI31Strategy from './resolve.js';

const normalize = (element) => {
if (!isObjectElement(element)) return element;
if (element.hasKey('$$normalized')) return element;

const plugins = [
refractorPluginNormalizeOperationIds({
Expand All @@ -36,7 +36,6 @@ const normalize = (element) => {
visitorOptions: { keyMap, nodeTypeGetter: getNodeType },
});

normalized.set('$$normalized', true);
return normalized;
};

Expand All @@ -46,18 +45,20 @@ const normalize = (element) => {
* Plain Old JavaScript Objects and returns Plain Old JavaScript Objects.
*/
export const pojoAdapter = (normalizeFn) => (spec) => {
if (spec?.$$normalized) return spec;
if (pojoAdapter.cache.has(spec)) return pojoAdapter.cache.get(spec);

const openApiElement = OpenApi3_1Element.refract(spec);
openApiElement.classes.push('result');

const normalized = normalizeFn(openApiElement);
const value = toValue(normalized);

pojoAdapter.cache.set(spec, value);
/**
* We're setting the cache here to avoid repeated refracting
* in `openapi-3-1-apidom` strategy resolve method.
*/
resolveOpenAPI31Strategy.cache.set(value, normalized);

return value;
return toValue(normalized);
};
pojoAdapter.cache = new WeakMap();

export default normalize;
/* eslint-enable camelcase */
5 changes: 3 additions & 2 deletions src/resolver/strategies/openapi-3-1-apidom/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import OpenAPI3_1ResolveStrategy from '@swagger-api/apidom-reference/resolve/str

import { DEFAULT_BASE_URL } from '../../../constants.js';
import * as optionsUtil from '../../utils/options.js';
import normalize from './normalize.js';
import HTTPResolverSwaggerClient from '../../apidom/reference/resolve/resolvers/http-swagger-client/index.js';
import JSONParser from '../../apidom/reference/parse/parsers/json/index.js';
import YAMLParser from '../../apidom/reference/parse/parsers/yaml-1-2/index.js';
Expand Down Expand Up @@ -62,9 +61,11 @@ const resolveOpenAPI31Strategy = async (options) => {
parameterMacro = null,
modelPropertyMacro = null,
mode = 'non-strict',
strategies,
} = options;
try {
const { cache } = resolveOpenAPI31Strategy;
const strategy = strategies.find((strg) => strg.match(spec));

// determining BaseURI
const cwd = url.isHttpUrl(url.cwd()) ? url.cwd() : DEFAULT_BASE_URL;
Expand Down Expand Up @@ -155,7 +156,7 @@ const resolveOpenAPI31Strategy = async (options) => {
},
});
const transcluded = transclude(fragmentElement, dereferenced, openApiElement);
const normalized = skipNormalization ? transcluded : normalize(transcluded);
const normalized = skipNormalization ? transcluded : strategy.normalize(transcluded);

return { spec: toValue(normalized), errors };
} catch (error) {
Expand Down
10 changes: 6 additions & 4 deletions src/subtree-resolver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import resolve from '../resolver/index.js';
import genericResolverStrategy from '../resolver/strategies/generic/index.js';
import openApi2ResolverStrategy from '../resolver/strategies/openapi-2/index.js';
import openApi30ResolverStrategy from '../resolver/strategies/openapi-3-0/index.js';
import { isOpenAPI31 } from '../helpers/openapi-predicates.js';

const resolveSubtree = async (obj, path, options = {}) => {
const {
Expand All @@ -47,13 +48,14 @@ const resolveSubtree = async (obj, path, options = {}) => {
useCircularStructures,
strategies,
};
const strategy = strategies.find((strg) => strg.match(resolveOptions));
const normalized = strategy.normalize(resolveOptions);
const strategy = strategies.find((strg) => strg.match(obj));
const normalized = strategy.normalize(obj);

const result = await resolve({
...resolveOptions,
spec: normalized,
...resolveOptions,
allowMetaPatches: true,
skipNormalization: true,
skipNormalization: !isOpenAPI31(obj),
});

if (!returnEntireTree && Array.isArray(path) && path.length) {
Expand Down
Loading