Skip to content

Commit aa3710b

Browse files
authored
feat(reference): add OpenAPI 2.0 dereference strategy (#3435)
Refs #3102
1 parent 7cb6980 commit aa3710b

File tree

66 files changed

+2019
-20
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2019
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { isObjectElement, Element } from '@swagger-api/apidom-core';
1+
import { isObjectElement, ObjectElement } from '@swagger-api/apidom-core';
2+
3+
export interface JSONReferenceLikeElement extends ObjectElement {
4+
hasKey: (value: '$ref') => true;
5+
}
26

37
// eslint-disable-next-line import/prefer-default-export
4-
export const isJSONReferenceLikeElement = <T extends Element>(element: T): boolean => {
5-
// @ts-ignore
8+
export const isJSONReferenceLikeElement = (
9+
element: unknown,
10+
): element is JSONReferenceLikeElement => {
611
return isObjectElement(element) && element.hasKey('$ref');
712
};

packages/apidom-ns-openapi-2/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export {
1212
} from '@swagger-api/apidom-core';
1313
export {
1414
isJSONReferenceElement,
15+
isJSONReferenceLikeElement,
1516
JSONReferenceElement,
1617
} from '@swagger-api/apidom-ns-json-schema-draft-4';
1718

@@ -33,6 +34,7 @@ export {
3334
isLicenseElement,
3435
isPathsElement,
3536
isPathItemElement,
37+
isPathItemElementExternal,
3638
isOperationElement,
3739
isExternalDocumentationElement,
3840
isParameterElement,
@@ -44,7 +46,9 @@ export {
4446
isHeaderElement,
4547
isTagElement,
4648
isReferenceElement,
49+
isReferenceElementExternal,
4750
isSchemaElement,
51+
isJSONReferenceElementExternal,
4852
isXmlElement,
4953
isDefinitionsElement,
5054
isParametersDefinitionsElement,

packages/apidom-ns-openapi-2/src/predicates.ts

+55-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { createPredicate } from '@swagger-api/apidom-core';
1+
import {
2+
createPredicate,
3+
ElementPredicate,
4+
isStringElement,
5+
toValue,
6+
} from '@swagger-api/apidom-core';
7+
import {
8+
isJSONReferenceElement,
9+
JSONReferenceElement,
10+
} from '@swagger-api/apidom-ns-json-schema-draft-4';
211

312
import SwaggerElement from './elements/Swagger';
413
import SwaggerVersionElement from './elements/SwaggerVersion';
@@ -98,6 +107,21 @@ export const isPathItemElement = createPredicate(
98107
},
99108
);
100109

110+
export const isPathItemElementExternal: ElementPredicate<PathItemElement> = (
111+
element: unknown,
112+
): element is PathItemElement => {
113+
if (!isPathItemElement(element)) {
114+
return false;
115+
}
116+
if (!isStringElement(element.$ref)) {
117+
return false;
118+
}
119+
120+
const value = toValue(element.$ref);
121+
122+
return typeof value === 'string' && value.length > 0 && !value.startsWith('#');
123+
};
124+
101125
export const isOperationElement = createPredicate(
102126
({ hasBasicElementProps, isElementType, primitiveEq }) => {
103127
return (element: unknown): element is OperationElement =>
@@ -208,6 +232,21 @@ export const isReferenceElement = createPredicate(
208232
},
209233
);
210234

235+
export const isReferenceElementExternal: ElementPredicate<ReferenceElement> = (
236+
element: unknown,
237+
): element is ReferenceElement => {
238+
if (!isReferenceElement(element)) {
239+
return false;
240+
}
241+
if (!isStringElement(element.$ref)) {
242+
return false;
243+
}
244+
245+
const value = toValue(element.$ref);
246+
247+
return typeof value === 'string' && value.length > 0 && !value.startsWith('#');
248+
};
249+
211250
export const isSchemaElement = createPredicate(
212251
({ hasBasicElementProps, isElementType, primitiveEq }) => {
213252
return (element: unknown): element is SchemaElement =>
@@ -218,6 +257,21 @@ export const isSchemaElement = createPredicate(
218257
},
219258
);
220259

260+
export const isJSONReferenceElementExternal: ElementPredicate<JSONReferenceElement> = (
261+
element: unknown,
262+
): element is PathItemElement => {
263+
if (!isJSONReferenceElement(element)) {
264+
return false;
265+
}
266+
if (!isStringElement(element.$ref)) {
267+
return false;
268+
}
269+
270+
const value = toValue(element.$ref);
271+
272+
return typeof value === 'string' && value.length > 0 && !value.startsWith('#');
273+
};
274+
221275
export const isXmlElement = createPredicate(
222276
({ hasBasicElementProps, isElementType, primitiveEq }) => {
223277
return (element: unknown): element is XmlElement =>

packages/apidom-reference/README.md

+31-12
Original file line numberDiff line numberDiff line change
@@ -1099,9 +1099,9 @@ It's possible to **change** strategies **order globally** by mutating global `re
10991099

11001100
```js
11011101
import { options } from '@swagger-api/apidom-reference';
1102-
import { AsyncApi2ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2';
1103-
import { OpenApi3_0ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0';
1104-
import { OpenApi3_1ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1';
1102+
import AsyncApi2ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2';
1103+
import OpenApi3_0ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0';
1104+
import OpenApi3_1ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1';
11051105

11061106
options.resolve.strategies = [
11071107
OpenApi3_0ResolveStrategy(),
@@ -1114,9 +1114,9 @@ To **change** the strategies **order** on ad-hoc basis:
11141114

11151115
```js
11161116
import { resolve } from '@swagger-api/apidom-reference';
1117-
import { AsyncApi2ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2';
1118-
import { OpenApi3_0ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0';
1119-
import { OpenApi3_1ResolveStrategy } from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1';
1117+
import AsyncApi2ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/asyncapi-2';
1118+
import OpenApi3_0ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-0';
1119+
import OpenApi3_1ResolveStrategy from '@swagger-api/apidom-reference/resolve/strategies/openapi-3-1';
11201120

11211121

11221122
await resolve('/home/user/oas.json', {
@@ -1361,6 +1361,20 @@ Supported media types:
13611361
]
13621362
```
13631363

1364+
##### [openapi-2](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/dereference/strategies/openapi-2)
1365+
1366+
Dereference strategy for dereferencing [OpenApi 2.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md) definitions.
1367+
1368+
Supported media types:
1369+
1370+
```js
1371+
[
1372+
'application/vnd.oai.openapi;version=2.0',
1373+
'application/vnd.oai.openapi+json;version=2.0',
1374+
'application/vnd.oai.openapi+yaml;version=2.0',
1375+
]
1376+
```
1377+
13641378
##### [openapi-3-0](https://github.com/swagger-api/apidom/tree/main/packages/apidom-reference/src/dereference/strategies/openapi-3-0)
13651379

13661380
Dereference strategy for dereferencing [OpenApi 3.0.x](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md) definitions.
@@ -1410,6 +1424,7 @@ returns `true` or until entire list of strategies is exhausted (throws error).
14101424

14111425
```js
14121426
[
1427+
OpenApi2DereferenceStrategy(),
14131428
OpenApi3_0DereferenceStrategy(),
14141429
OpenApi3_1DereferenceStrategy(),
14151430
AsyncApi2DereferenceStrategy(),
@@ -1421,11 +1436,13 @@ It's possible to **change** strategies **order globally** by mutating global `de
14211436

14221437
```js
14231438
import { options } from '@swagger-api/apidom-reference';
1424-
import { AsyncApi2DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2'
1425-
import { OpenApi3_0DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0'
1426-
import { OpenApi3_1DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1'
1439+
import AsyncApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2'
1440+
import OpenApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-2'
1441+
import OpenApi3_0DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0'
1442+
import OpenApi3_1DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1'
14271443

14281444
options.dereference.strategies = [
1445+
OpenApi2DereferenceStrategy(),
14291446
OpenApi3_0DereferenceStrategy(),
14301447
OpenApi3_1DereferenceStrategy(),
14311448
AsyncApi2DereferenceStrategy(),
@@ -1436,9 +1453,10 @@ To **change** the strategies **order** on ad-hoc basis:
14361453

14371454
```js
14381455
import { dereference } from '@swagger-api/apidom-reference';
1439-
import { AsyncApi2DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2'
1440-
import { OpenApi3_0DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0'
1441-
import { OpenApi3_1DereferenceStrategy } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1'
1456+
import AsyncApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/asyncapi-2'
1457+
import OpenApi2DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-2'
1458+
import OpenApi3_0DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-0'
1459+
import OpenApi3_1DereferenceStrategy from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1'
14421460

14431461
await dereference('/home/user/oas.json', {
14441462
parse: {
@@ -1447,6 +1465,7 @@ await dereference('/home/user/oas.json', {
14471465
dereference: {
14481466
strategies: [
14491467
AsyncApi2DereferenceStrategy(),
1468+
OpenApi2DereferenceStrategy(),
14501469
OpenApi3_0DereferenceStrategy(),
14511470
OpenApi3_1DereferenceStrategy(),
14521471
]

packages/apidom-reference/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@
145145
"require": "./cjs/dereference/strategies/asyncapi-2/index.cjs",
146146
"types": "./types/dereference/strategies/asyncapi-2/index.d.ts"
147147
},
148+
"./dereference/strategies/openapi-2": {
149+
"import": "./es/dereference/strategies/openapi-2/index.mjs",
150+
"require": "./cjs/dereference/strategies/openapi-2/index.cjs",
151+
"types": "./types/dereference/strategies/openapi-2/index.d.ts"
152+
},
148153
"./dereference/strategies/openapi-3-0": {
149154
"import": "./es/dereference/strategies/openapi-3-0/index.mjs",
150155
"require": "./cjs/dereference/strategies/openapi-3-0/index.cjs",

packages/apidom-reference/src/configuration/saturated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import AsyncApiYaml2Parser from '../parse/parsers/asyncapi-yaml-2';
1616
import JsonParser from '../parse/parsers/json';
1717
import YamlParser from '../parse/parsers/yaml-1-2';
1818
import BinaryParser from '../parse/parsers/binary/index-node';
19+
import OpenApi2DereferenceStrategy from '../dereference/strategies/openapi-2';
1920
import OpenApi3_0DereferenceStrategy from '../dereference/strategies/openapi-3-0';
2021
import OpenApi3_1DereferenceStrategy from '../dereference/strategies/openapi-3-1';
2122
import AsyncApi2DereferenceStrategy from '../dereference/strategies/asyncapi-2';
@@ -49,6 +50,7 @@ options.resolve.strategies = [
4950
];
5051

5152
options.dereference.strategies = [
53+
OpenApi2DereferenceStrategy(),
5254
OpenApi3_0DereferenceStrategy(),
5355
OpenApi3_1DereferenceStrategy(),
5456
AsyncApi2DereferenceStrategy(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import stampit from 'stampit';
2+
import { defaultTo, propEq } from 'ramda';
3+
import { createNamespace, visit, Element } from '@swagger-api/apidom-core';
4+
import openApi2Namespace, {
5+
getNodeType,
6+
isSwaggerElement,
7+
keyMap,
8+
mediaTypes,
9+
} from '@swagger-api/apidom-ns-openapi-2';
10+
11+
import DereferenceStrategy from '../DereferenceStrategy';
12+
import {
13+
DereferenceStrategy as IDereferenceStrategy,
14+
File as IFile,
15+
ReferenceOptions as IReferenceOptions,
16+
} from '../../../types';
17+
import Reference from '../../../Reference';
18+
import ReferenceSet from '../../../ReferenceSet';
19+
import OpenApi2DereferenceVisitor from './visitor';
20+
21+
// @ts-ignore
22+
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
23+
24+
const OpenApi2DereferenceStrategy: stampit.Stamp<IDereferenceStrategy> = stampit(
25+
DereferenceStrategy,
26+
{
27+
init() {
28+
this.name = 'openapi-2';
29+
},
30+
methods: {
31+
canDereference(file: IFile): boolean {
32+
// assert by media type
33+
if (file.mediaType !== 'text/plain') {
34+
return mediaTypes.includes(file.mediaType);
35+
}
36+
37+
// assert by inspecting ApiDOM
38+
return isSwaggerElement(file.parseResult?.api);
39+
},
40+
41+
async dereference(file: IFile, options: IReferenceOptions): Promise<Element> {
42+
const namespace = createNamespace(openApi2Namespace);
43+
const refSet = defaultTo(ReferenceSet(), options.dereference.refSet);
44+
let reference;
45+
46+
if (!refSet.has(file.uri)) {
47+
reference = Reference({ uri: file.uri, value: file.parseResult });
48+
refSet.add(reference);
49+
} else {
50+
// pre-computed refSet was provided as configuration option
51+
reference = refSet.find(propEq(file.uri, 'uri'));
52+
}
53+
54+
const visitor = OpenApi2DereferenceVisitor({ reference, namespace, options });
55+
const dereferencedElement = await visitAsync(refSet.rootRef.value, visitor, {
56+
keyMap,
57+
nodeTypeGetter: getNodeType,
58+
});
59+
60+
/**
61+
* Release all memory if this refSet was not provided as an configuration option.
62+
* If provided as configuration option, then provider is responsible for cleanup.
63+
*/
64+
if (options.dereference.refSet === null) {
65+
refSet.clean();
66+
}
67+
68+
return dereferencedElement;
69+
},
70+
},
71+
},
72+
);
73+
74+
export default OpenApi2DereferenceStrategy;

0 commit comments

Comments
 (0)