Skip to content

Commit f273e1c

Browse files
committed
feat(reference): add support for useCircularStructures option
This affects OpenAPI 3.1 swagger-client dereference strategy. Refs #2328
1 parent 824ba5a commit f273e1c

File tree

14 files changed

+329
-15
lines changed

14 files changed

+329
-15
lines changed

packages/apidom-reference/src/dereference/strategies/openapi-3-1-swagger-client/index.ts

+25-9
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ import OpenApi3_1DereferenceVisitor from './visitor';
2222
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
2323

2424
// eslint-disable-next-line @typescript-eslint/naming-convention
25-
const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp<IDereferenceStrategy> = stampit(
26-
DereferenceStrategy,
27-
{
28-
init() {
29-
this.name = 'openapi-3-1';
25+
interface IOpenApi3_1SwaggerClientDereferenceStrategy extends IDereferenceStrategy {
26+
useCircularStructures: boolean;
27+
}
28+
29+
// eslint-disable-next-line @typescript-eslint/naming-convention
30+
const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp<IOpenApi3_1SwaggerClientDereferenceStrategy> =
31+
stampit(DereferenceStrategy, {
32+
props: {
33+
useCircularStructures: true,
34+
},
35+
init(
36+
this: IOpenApi3_1SwaggerClientDereferenceStrategy,
37+
{ useCircularStructures = this.useCircularStructures } = {},
38+
) {
39+
// @ts-ignore
40+
this.name = 'openapi-3-1-swagger-client';
41+
this.useCircularStructures = useCircularStructures;
3042
},
3143
methods: {
3244
canDereference(file: IFile): boolean {
@@ -52,14 +64,19 @@ const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp<IDereferenceStra
5264
reference = refSet.find(propEq('uri', file.uri));
5365
}
5466

55-
const visitor = OpenApi3_1DereferenceVisitor({ reference, namespace, options });
67+
const visitor = OpenApi3_1DereferenceVisitor({
68+
reference,
69+
namespace,
70+
options,
71+
useCircularStructures: this.useCircularStructures,
72+
});
5673
const dereferencedElement = await visitAsync(refSet.rootRef.value, visitor, {
5774
keyMap,
5875
nodeTypeGetter: getNodeType,
5976
});
6077

6178
/**
62-
* Release all memory if this refSet was not provided as an configuration option.
79+
* Release all memory if this refSet was not provided as a configuration option.
6380
* If provided as configuration option, then provider is responsible for cleanup.
6481
*/
6582
if (options.dereference.refSet === null) {
@@ -69,7 +86,6 @@ const OpenApi3_1SwaggerClientDereferenceStrategy: stampit.Stamp<IDereferenceStra
6986
return dereferencedElement;
7087
},
7188
},
72-
},
73-
);
89+
});
7490

7591
export default OpenApi3_1SwaggerClientDereferenceStrategy;

packages/apidom-reference/src/dereference/strategies/openapi-3-1-swagger-client/visitor.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
maybeRefractToSchemaElement,
4444
} from '../../../resolve/strategies/openapi-3-1/util';
4545
import EvaluationJsonSchemaUriError from '../openapi-3-1/selectors/uri/errors/EvaluationJsonSchemaUriError';
46+
import { isHttpUrl } from '../../../util/url';
4647

4748
// @ts-ignore
4849
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
@@ -55,13 +56,22 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = stampit({
5556
namespace: null,
5657
reference: null,
5758
options: null,
59+
useCircularStructures: true,
5860
},
59-
init({ indirections = [], visited = new WeakSet(), reference, namespace, options }) {
61+
init({
62+
indirections = [],
63+
visited = new WeakSet(),
64+
reference,
65+
namespace,
66+
options,
67+
useCircularStructures,
68+
}) {
6069
this.indirections = indirections;
6170
this.visited = visited;
6271
this.namespace = namespace;
6372
this.reference = reference;
6473
this.options = options;
74+
this.useCircularStructures = useCircularStructures;
6575
},
6676
methods: {
6777
toBaseURI(uri: string): string {
@@ -458,13 +468,28 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = stampit({
458468
);
459469
}
460470

471+
// detect possible cycle and avoid it
472+
if (!this.useCircularStructures && this.visited.has(referencedElement)) {
473+
// make the referencing URL absolute if possible
474+
if (isHttpUrl(reference.uri) && isStringElement(referencingElement.$ref)) {
475+
const absoluteJSONPointerURL = url.resolve(
476+
reference.uri,
477+
referencingElement.$ref?.toValue(),
478+
);
479+
referencingElement.set('$ref', absoluteJSONPointerURL);
480+
}
481+
// skip processing this node
482+
return false;
483+
}
484+
461485
// dive deep into the fragment
462486
const visitor: any = OpenApi3_1SwaggerClientDereferenceVisitor({
463487
reference,
464488
namespace: this.namespace,
465489
indirections: [...this.indirections],
466490
options: this.options,
467491
visited: this.visited,
492+
useCircularStructures: this.useCircularStructures,
468493
});
469494
referencedElement = await visitAsync(referencedElement, visitor, {
470495
keyMap,

packages/apidom-reference/test/dereference/strategies/openapi-3-1-swagger-client/bootstrap.ts

+13
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import YamlParser from './helpers/parsers/yaml1-2';
77
import OpenApiJson3_1Parser from './helpers/parsers/openapi-json-3-1';
88
import OpenApiYaml3_1Parser from './helpers/parsers/openapi-yaml-3-1';
99
import HttpResolverSwaggerClient from '../../../../src/resolve/resolvers/HttpResolverSwaggerClient';
10+
import OpenApi3_1SwaggerClientDereferenceStrategy from '../../../../src/dereference/strategies/openapi-3-1-swagger-client';
1011

1112
const originalParsers = [...options.parse.parsers];
1213
const originalResolvers = [...options.resolve.resolvers];
14+
const originalDereferenceStrategies = [...options.dereference.strategies];
1315

1416
export const before = () => {
1517
// configure custom parser plugins globally
@@ -43,9 +45,20 @@ export const before = () => {
4345

4446
return resolver;
4547
});
48+
49+
// configure custom dereference strategy globally
50+
options.dereference.strategies = options.dereference.strategies.map((strategy) => {
51+
// @ts-ignore
52+
if (strategy.name === 'openapi-3-1') {
53+
return OpenApi3_1SwaggerClientDereferenceStrategy();
54+
}
55+
56+
return strategy;
57+
});
4658
};
4759

4860
export const after = () => {
4961
options.parse.parsers = originalParsers;
5062
options.resolve.resolvers = originalResolvers;
63+
options.dereference.strategies = originalDereferenceStrategies;
5164
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
{
3+
"openapi": "3.1.0",
4+
"components": {
5+
"schemas": {
6+
"User": {
7+
"type": "object",
8+
"properties": {
9+
"profile": {
10+
"properties": {
11+
"parent": {
12+
"$ref": "http://localhost:8123/ex.json#/$defs/UserProfile"
13+
}
14+
}
15+
}
16+
}
17+
}
18+
}
19+
}
20+
}
21+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$defs": {
3+
"UserProfile": {
4+
"properties": {
5+
"parent": {
6+
"$ref": "#/$defs/UserProfile"
7+
}
8+
}
9+
}
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"openapi": "3.1.0",
3+
"components": {
4+
"schemas": {
5+
"User": {
6+
"type": "object",
7+
"properties": {
8+
"profile": {
9+
"$ref": "./ex.json#/$defs/UserProfile"
10+
}
11+
}
12+
}
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
{
3+
"openapi": "3.1.0",
4+
"components": {
5+
"schemas": {
6+
"User": {
7+
"type": "object",
8+
"properties": {
9+
"profile": {
10+
"properties": {
11+
"parent": {
12+
"$ref": "#/$defs/UserProfile"
13+
}
14+
}
15+
}
16+
}
17+
}
18+
}
19+
}
20+
}
21+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$defs": {
3+
"UserProfile": {
4+
"properties": {
5+
"parent": {
6+
"$ref": "#/$defs/UserProfile"
7+
}
8+
}
9+
}
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"openapi": "3.1.0",
3+
"components": {
4+
"schemas": {
5+
"User": {
6+
"type": "object",
7+
"properties": {
8+
"profile": {
9+
"$ref": "./ex.json#/$defs/UserProfile"
10+
}
11+
}
12+
}
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"openapi": "3.1.0",
4+
"components": {
5+
"schemas": {
6+
"User": {
7+
"type": "object",
8+
"properties": {
9+
"parent": {
10+
"$ref": "http://localhost:8123/root.json#/components/schemas/User"
11+
}
12+
}
13+
}
14+
}
15+
}
16+
}
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"openapi": "3.1.0",
3+
"components": {
4+
"schemas": {
5+
"User": {
6+
"type": "object",
7+
"properties": {
8+
"parent": {
9+
"$ref": "#/components/schemas/User"
10+
}
11+
}
12+
}
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
{
3+
"openapi": "3.1.0",
4+
"components": {
5+
"schemas": {
6+
"User": {
7+
"type": "object",
8+
"properties": {
9+
"parent": {
10+
"$ref": "#/components/schemas/User"
11+
}
12+
}
13+
}
14+
}
15+
}
16+
}
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"openapi": "3.1.0",
3+
"components": {
4+
"schemas": {
5+
"User": {
6+
"type": "object",
7+
"properties": {
8+
"parent": {
9+
"$ref": "#/components/schemas/User"
10+
}
11+
}
12+
}
13+
}
14+
}
15+
}

0 commit comments

Comments
 (0)