Skip to content

Commit

Permalink
feat(resolver): align dereferencing with ApiDOM Dereference Architect…
Browse files Browse the repository at this point in the history
…ure 2.0 (#3472)

APiDOM Deference Architecture 2.0 - swagger-api/apidom#3915

Refs #3467
  • Loading branch information
char0n committed Apr 17, 2024
1 parent 92bba0d commit 722251f
Show file tree
Hide file tree
Showing 20 changed files with 666 additions and 725 deletions.
1 change: 1 addition & 0 deletions config/jest/jest.unit.config.js
Expand Up @@ -14,4 +14,5 @@ module.exports = {
'/__fixtures__/',
'/__utils__/',
],
silent: true,
};
8 changes: 4 additions & 4 deletions package-lock.json

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

8 changes: 4 additions & 4 deletions package.json
Expand Up @@ -110,11 +110,11 @@
},
"dependencies": {
"@babel/runtime-corejs3": "^7.22.15",
"@swagger-api/apidom-core": ">=0.99.0 <1.0.0",
"@swagger-api/apidom-core": ">=0.99.1 <1.0.0",
"@swagger-api/apidom-error": ">=0.99.0 <1.0.0",
"@swagger-api/apidom-json-pointer": ">=0.99.0 <1.0.0",
"@swagger-api/apidom-ns-openapi-3-1": ">=0.99.0 <1.0.0",
"@swagger-api/apidom-reference": ">=0.99.0 <1.0.0",
"@swagger-api/apidom-json-pointer": ">=0.99.1 <1.0.0",
"@swagger-api/apidom-ns-openapi-3-1": ">=0.99.1 <1.0.0",
"@swagger-api/apidom-reference": ">=0.99.1 <1.0.0",
"cookie": "~0.6.0",
"deepmerge": "~4.3.0",
"fast-json-patch": "^3.0.0-1",
Expand Down
@@ -1,5 +1,5 @@
/* eslint-disable camelcase */
import { createNamespace, visit, mergeAllVisitors } from '@swagger-api/apidom-core';
import { createNamespace, visit, mergeAllVisitors, 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';
Expand All @@ -14,23 +14,20 @@ const mergeAllVisitorsAsync = mergeAllVisitors[Symbol.for('nodejs.util.promisify

const OpenApi3_1SwaggerClientDereferenceStrategy = OpenApi3_1DereferenceStrategy.compose({
props: {
useCircularStructures: true,
allowMetaPatches: false,
parameterMacro: null,
modelPropertyMacro: null,
mode: 'non-strict',
ancestors: null,
},
init({
useCircularStructures = this.useCircularStructures,
allowMetaPatches = this.allowMetaPatches,
parameterMacro = this.parameterMacro,
modelPropertyMacro = this.modelPropertyMacro,
mode = this.mode,
ancestors = [],
} = {}) {
this.name = 'openapi-3-1-swagger-client';
this.useCircularStructures = useCircularStructures;
this.allowMetaPatches = allowMetaPatches;
this.parameterMacro = parameterMacro;
this.modelPropertyMacro = modelPropertyMacro;
Expand All @@ -41,23 +38,41 @@ const OpenApi3_1SwaggerClientDereferenceStrategy = OpenApi3_1DereferenceStrategy
async dereference(file, options) {
const visitors = [];
const namespace = createNamespace(openApi3_1Namespace);
const refSet = options.dereference.refSet ?? ReferenceSet();
const immutableRefSet = options.dereference.refSet ?? ReferenceSet();
const mutableRefsSet = ReferenceSet();
let refSet = immutableRefSet;
let reference;

if (!refSet.has(file.uri)) {
if (!immutableRefSet.has(file.uri)) {
reference = Reference({ uri: file.uri, value: file.parseResult });
refSet.add(reference);
immutableRefSet.add(reference);
} else {
// pre-computed refSet was provided as configuration option
reference = refSet.find((ref) => ref.uri === file.uri);
reference = immutableRefSet.find((ref) => ref.uri === file.uri);
}

/**
* Clone refSet due the dereferencing process being mutable.
* We don't want to mutate the original refSet and the references.
*/
if (options.dereference.immutable) {
immutableRefSet.refs
.map((ref) =>
Reference({
...ref,
value: cloneDeep(ref.value),
})
)
.forEach((ref) => mutableRefsSet.add(ref));
reference = mutableRefsSet.find((ref) => ref.uri === file.uri);
refSet = mutableRefsSet;
}

// create main dereference visitor
const dereferenceVisitor = OpenApi3_1SwaggerClientDereferenceVisitor({
reference,
namespace,
options,
useCircularStructures: this.useCircularStructures,
allowMetaPatches: this.allowMetaPatches,
ancestors: this.ancestors,
});
Expand Down Expand Up @@ -96,13 +111,32 @@ const OpenApi3_1SwaggerClientDereferenceStrategy = OpenApi3_1DereferenceStrategy
});

/**
* Release all memory if this refSet was not provided as a configuration option.
* If immutable option is set, replay refs from the refSet.
*/
if (options.dereference.immutable) {
mutableRefsSet.refs
.filter((ref) => ref.uri.startsWith('immutable://'))
.map((ref) =>
Reference({
...ref,
uri: ref.uri.replace(/^immutable:\/\//, ''),
})
)
.forEach((ref) => immutableRefSet.add(ref));
reference = immutableRefSet.find((ref) => ref.uri === file.uri);
refSet = immutableRefSet;
}

/**
* Release all memory if this refSet was not provided as an configuration option.
* If provided as configuration option, then provider is responsible for cleanup.
*/
if (options.dereference.refSet === null) {
refSet.clean();
immutableRefSet.clean();
}

mutableRefsSet.clean();

return dereferencedElement;
},
},
Expand Down
@@ -1,5 +1,5 @@
import { isArrayElement, deepmerge, cloneDeep, toValue } from '@swagger-api/apidom-core';
import { isSchemaElement, SchemaElement } from '@swagger-api/apidom-ns-openapi-3-1';
import { isArrayElement, deepmerge } from '@swagger-api/apidom-core';
import { isSchemaElement } from '@swagger-api/apidom-ns-openapi-3-1';

import compose from '../utils/compose.js';
import toPath from '../utils/to-path.js';
Expand All @@ -26,11 +26,8 @@ const AllOfVisitor = compose({

// remove allOf keyword if empty
if (schemaElement.allOf.isEmpty) {
return new SchemaElement(
schemaElement.content.filter((memberElement) => toValue(memberElement.key) !== 'allOf'),
cloneDeep(schemaElement.meta),
cloneDeep(schemaElement.attributes)
);
schemaElement.remove('allOf');
return undefined;
}

// collect errors if allOf keyword contains anything else than Schema Object
Expand All @@ -42,37 +39,45 @@ const AllOfVisitor = compose({
return undefined;
}

const mergedSchemaElement = deepmerge.all([...schemaElement.allOf.content, schemaElement]);
while (schemaElement.hasKey('allOf')) {
const { allOf } = schemaElement;
schemaElement.remove('allOf');
const allOfMerged = deepmerge.all([...allOf.content, schemaElement]);

/**
* If there was not an original $$ref value, make sure to remove
* any $$ref value that may exist from the result of `allOf` merges.
*/
if (!schemaElement.hasKey('$$ref')) {
mergedSchemaElement.remove('$$ref');
}
/**
* If there was not an original $$ref value, make sure to remove
* any $$ref value that may exist from the result of `allOf` merges.
*/
if (!schemaElement.hasKey('$$ref')) {
allOfMerged.remove('$$ref');
}

/**
* If there was an example keyword in the original definition,
* keep it instead of merging with example from other schema.
*/
if (schemaElement.hasKey('example')) {
const member = mergedSchemaElement.getMember('example');
member.value = schemaElement.get('example');
}
/**
* If there was an example keyword in the original schema,
* keep it instead of merging with example from other schema.
*/
if (schemaElement.hasKey('example')) {
const member = allOfMerged.getMember('example');
if (member) {
member.value = schemaElement.get('example');
}
}

/**
* If there was an examples keyword in the original schema,
* keep it instead of merging with examples from other schema.
*/
if (schemaElement.hasKey('examples')) {
const member = allOfMerged.getMember('examples');
if (member) {
member.value = schemaElement.get('examples');
}
}

/**
* If there was an examples keyword in the original definition,
* keep it instead of merging with examples from other schema.
*/
if (schemaElement.hasKey('examples')) {
const member = mergedSchemaElement.getMember('examples');
member.value = schemaElement.get('examples');
schemaElement.content = allOfMerged.content;
}

// remove allOf keyword after the merge
mergedSchemaElement.remove('allOf');
return mergedSchemaElement;
return undefined;
},
},
},
Expand Down

0 comments on commit 722251f

Please sign in to comment.