Skip to content

Commit

Permalink
fix(resolver): apply propertyMacro, parameterMacro and allOf plugins …
Browse files Browse the repository at this point in the history
…for OpenAPI 3.1.0 (#3521)

Refs #3520

---------

Co-authored-by: Vladimir Gorej <[email protected]>
  • Loading branch information
glowcloud and char0n authored May 21, 2024
1 parent 75cf1ad commit 2eb34c9
Show file tree
Hide file tree
Showing 13 changed files with 1,997 additions and 2,306 deletions.
2 changes: 1 addition & 1 deletion config/jest/jest.unit.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ module.exports = {
'/__fixtures__/',
'/__utils__/',
],
silent: false,
silent: true,
};
3,616 changes: 1,394 additions & 2,222 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 @@ -73,11 +73,11 @@
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime-corejs3": "^7.22.15",
"@swagger-api/apidom-core": ">=1.0.0-alpha.1 <1.0.0-beta.0",
"@swagger-api/apidom-core": ">=1.0.0-alpha.3 <1.0.0-beta.0",
"@swagger-api/apidom-error": ">=1.0.0-alpha.1 <1.0.0-beta.0",
"@swagger-api/apidom-json-pointer": ">=1.0.0-alpha.1 <1.0.0-beta.0",
"@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-alpha.1 <1.0.0-beta.0",
"@swagger-api/apidom-reference": ">=1.0.0-alpha.1 <1.0.0-beta.0",
"@swagger-api/apidom-json-pointer": ">=1.0.0-alpha.3 <1.0.0-beta.0",
"@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-alpha.3 <1.0.0-beta.0",
"@swagger-api/apidom-reference": ">=1.0.0-alpha.3 <1.0.0-beta.0",
"cookie": "~0.6.0",
"deepmerge": "~4.3.0",
"fast-json-patch": "^3.0.0-1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
/* eslint-disable camelcase */
import { createNamespace, visit, mergeAllVisitors, cloneDeep } from '@swagger-api/apidom-core';
import { createNamespace, visit, cloneDeep } from '@swagger-api/apidom-core';
import { ReferenceSet, Reference } from '@swagger-api/apidom-reference/configuration/empty';
import OpenAPI3_1DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1';
import openApi3_1Namespace, { getNodeType, keyMap } from '@swagger-api/apidom-ns-openapi-3-1';

import OpenAPI3_1SwaggerClientDereferenceVisitor from './visitors/dereference.js';
import ParameterMacroVisitor from './visitors/parameters.js';
import ModelPropertyMacroVisitor from './visitors/properties.js';
import AllOfVisitor from './visitors/all-of.js';
import RootVisitor from './visitors/root.js';

const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
const mergeAllVisitorsAsync = mergeAllVisitors[Symbol.for('nodejs.util.promisify.custom')];

class OpenAPI3_1SwaggerClientDereferenceStrategy extends OpenAPI3_1DereferenceStrategy {
allowMetaPatches;
Expand Down Expand Up @@ -42,7 +38,6 @@ class OpenAPI3_1SwaggerClientDereferenceStrategy extends OpenAPI3_1DereferenceSt
}

async dereference(file, options) {
const visitors = [];
const namespace = createNamespace(openApi3_1Namespace);
const immutableRefSet = options.dereference.refSet ?? new ReferenceSet();
const mutableRefsSet = new ReferenceSet();
Expand Down Expand Up @@ -75,42 +70,16 @@ class OpenAPI3_1SwaggerClientDereferenceStrategy extends OpenAPI3_1DereferenceSt
refSet = mutableRefsSet;
}

// create main dereference visitor
const dereferenceVisitor = new OpenAPI3_1SwaggerClientDereferenceVisitor({
const rootVisitor = new RootVisitor({
reference,
namespace,
options,
allowMetaPatches: this.allowMetaPatches,
ancestors: this.ancestors,
modelPropertyMacro: this.modelPropertyMacro,
mode: this.mode,
parameterMacro: this.parameterMacro,
});
visitors.push(dereferenceVisitor);

// create parameter macro visitor (if necessary)
if (typeof this.parameterMacro === 'function') {
const parameterMacroVisitor = new ParameterMacroVisitor({
parameterMacro: this.parameterMacro,
options,
});
visitors.push(parameterMacroVisitor);
}

// create model property macro visitor (if necessary)
if (typeof this.modelPropertyMacro === 'function') {
const modelPropertyMacroVisitor = new ModelPropertyMacroVisitor({
modelPropertyMacro: this.modelPropertyMacro,
options,
});
visitors.push(modelPropertyMacroVisitor);
}

// create allOf visitor (if necessary)
if (this.mode !== 'strict') {
const allOfVisitor = new AllOfVisitor({ options });
visitors.push(allOfVisitor);
}

// establish root visitor by visitor merging
const rootVisitor = mergeAllVisitorsAsync(visitors, { nodeTypeGetter: getNodeType });

const dereferencedElement = await visitAsync(refSet.rootRef.value, rootVisitor, {
keyMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
// initialize element identity manager
const identityManager = new IdentityManager();

// custom mutation replacer
const mutationReplacer = (newElement, oldElement, key, parent) => {
if (isMemberElement(parent)) {
parent.value = newElement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = newElement; // eslint-disable-line no-param-reassign
}
};

class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVisitor {
useCircularStructures;

Expand All @@ -78,7 +87,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
this.basePath = basePath;
}

async ReferenceElement(referencingElement, key, parent, path, ancestors) {
async ReferenceElement(referencingElement, key, parent, path, ancestors, link) {
try {
// skip current referencing element as it's already been access
if (this.indirections.includes(referencingElement)) {
Expand Down Expand Up @@ -162,11 +171,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
this.options.dereference.circularReplacer;
const replacement = replacer(refElement);

if (isMemberElement(parent)) {
parent.value = replacement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = replacement; // eslint-disable-line no-param-reassign
}
link.replaceWith(refElement, mutationReplacer);

return !parent ? replacement : false;
}
Expand Down Expand Up @@ -258,11 +263,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
/**
* Transclude referencing element with merged referenced element.
*/
if (isMemberElement(parent)) {
parent.value = mergedElement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = mergedElement; // eslint-disable-line no-param-reassign
}
link.replaceWith(mergedElement, mutationReplacer);

/**
* We're at the root of the tree, so we're just replacing the entire tree.
Expand All @@ -282,7 +283,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
}
}

async PathItemElement(pathItemElement, key, parent, path, ancestors) {
async PathItemElement(pathItemElement, key, parent, path, ancestors, link) {
try {
// ignore PathItemElement without $ref field
if (!isStringElement(pathItemElement.$ref)) {
Expand Down Expand Up @@ -368,11 +369,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
this.options.dereference.circularReplacer;
const replacement = replacer(refElement);

if (isMemberElement(parent)) {
parent.value = replacement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = replacement; // eslint-disable-line no-param-reassign
}
link.replaceWith(refElement, mutationReplacer);

return !parent ? replacement : false;
}
Expand Down Expand Up @@ -464,11 +461,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
/**
* Transclude referencing element with merged referenced element.
*/
if (isMemberElement(parent)) {
parent.value = referencedElement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = referencedElement; // eslint-disable-line no-param-reassign
}
link.replaceWith(referencedElement, mutationReplacer);

/**
* We're at the root of the tree, so we're just replacing the entire tree.
Expand All @@ -488,7 +481,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
}
}

async SchemaElement(referencingElement, key, parent, path, ancestors) {
async SchemaElement(referencingElement, key, parent, path, ancestors, link) {
try {
// skip current referencing schema as $ref keyword was not defined
if (!isStringElement(referencingElement.$ref)) {
Expand Down Expand Up @@ -651,11 +644,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
this.options.dereference.circularReplacer;
const replacement = replacer(refElement);

if (isMemberElement(parent)) {
parent.value = replacement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = replacement; // eslint-disable-line no-param-reassign
}
link.replaceWith(replacement, mutationReplacer);

return !parent ? replacement : false;
}
Expand Down Expand Up @@ -721,11 +710,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
cloneDeep(identityManager.identify(referencingElement))
);

if (isMemberElement(parent)) {
parent.value = booleanJsonSchemaElement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = booleanJsonSchemaElement; // eslint-disable-line no-param-reassign
}
link.replaceWith(booleanJsonSchemaElement, mutationReplacer);

return !parent ? booleanJsonSchemaElement : false;
}
Expand Down Expand Up @@ -773,11 +758,7 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
/**
* Transclude referencing element with merged referenced element.
*/
if (isMemberElement(parent)) {
parent.value = referencedElement; // eslint-disable-line no-param-reassign
} else if (Array.isArray(parent)) {
parent[key] = referencedElement; // eslint-disable-line no-param-reassign
}
link.replaceWith(referencedElement, mutationReplacer);

/**
* We're at the root of the tree, so we're just replacing the entire tree.
Expand Down Expand Up @@ -807,9 +788,9 @@ class OpenAPI3_1SwaggerClientDereferenceVisitor extends OpenAPI3_1DereferenceVis
return undefined;
}

async ExampleElement(exampleElement, key, parent, path, ancestors) {
async ExampleElement(exampleElement, key, parent, path, ancestors, link) {
try {
return await super.ExampleElement(exampleElement, key, parent, path, ancestors);
return await super.ExampleElement(exampleElement, key, parent, path, ancestors, link);
} catch (error) {
const rootCause = getRootCause(error);
const wrappedError = wrapError(rootCause, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { mergeAllVisitors } from '@swagger-api/apidom-core';
import { getNodeType } from '@swagger-api/apidom-ns-openapi-3-1';

import ModelPropertyMacroVisitor from './properties.js';
import AllOfVisitor from './all-of.js';
import ParameterMacroVisitor from './parameters.js';
import OpenAPI3_1SwaggerClientDereferenceVisitor from './dereference.js'; // eslint-disable-line camelcase

const mergeAllVisitorsAsync = mergeAllVisitors[Symbol.for('nodejs.util.promisify.custom')];

class RootVisitor {
constructor({ parameterMacro, modelPropertyMacro, mode, options, ...rest }) {
const visitors = [];

visitors.push(
new OpenAPI3_1SwaggerClientDereferenceVisitor({
...rest,
options,
})
);

if (typeof modelPropertyMacro === 'function') {
visitors.push(new ModelPropertyMacroVisitor({ modelPropertyMacro, options }));
}

if (mode !== 'strict') {
visitors.push(new AllOfVisitor({ options }));
}

if (typeof parameterMacro === 'function') {
visitors.push(new ParameterMacroVisitor({ parameterMacro, options }));
}

const mergedVisitor = mergeAllVisitorsAsync(visitors, { nodeTypeGetter: getNodeType });
Object.assign(this, mergedVisitor);
}
}

export default RootVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-disable camelcase */
import { toValue } from '@swagger-api/apidom-core';
import { mediaTypes, OpenApi3_1Element } from '@swagger-api/apidom-ns-openapi-3-1';
import { dereferenceApiDOM } from '@swagger-api/apidom-reference/configuration/empty';

import * as jestSetup from '../__utils__/jest.local.setup.js';
import OpenAPI3_1SwaggerClientDereferenceStrategy from '../../../../../../../../src/resolver/apidom/reference/dereference/strategies/openapi-3-1-swagger-client/index.js';

describe('dereference', () => {
beforeAll(() => {
jestSetup.beforeAll();
});

afterAll(() => {
jestSetup.afterAll();
});

describe('strategies', () => {
describe('openapi-3-1-swagger-client', () => {
describe('Parametr Object', () => {
test('should resolve reference and apply modelPropertyMacro function', async () => {
const spec = OpenApi3_1Element.refract({
openapi: '3.1.0',
paths: {
'/': {
get: {
operationId: 'test',
parameters: [
{
$ref: '#/components/parameters/Baz',
},
],
},
},
},
components: {
parameters: {
Baz: {
name: 'baz',
in: 'query',
schema: {
type: 'object',
},
},
},
},
});

const dereferenced = await dereferenceApiDOM(spec, {
parse: { mediaType: mediaTypes.latest('json') },
dereference: {
strategies: [
new OpenAPI3_1SwaggerClientDereferenceStrategy({
parameterMacro: (operation, parameter) =>
`${operation.operationId}-${parameter.name}`,
}),
],
},
});

expect(toValue(dereferenced)).toEqual({
openapi: '3.1.0',
paths: {
'/': {
get: {
operationId: 'test',
parameters: [
{
name: 'baz',
in: 'query',
schema: { type: 'object' },
default: 'test-baz',
},
],
},
},
},
components: {
parameters: {
Baz: {
name: 'baz',
in: 'query',
schema: {
type: 'object',
},
},
},
},
});
});
});
});
});
});
/* eslint-enable camelcase */
Loading

0 comments on commit 2eb34c9

Please sign in to comment.