@speclynx/apidom-reference 6.1.2 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +57 -0
- package/dist/apidom-reference.browser.js +5783 -3741
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +31 -30
- package/src/File.cjs +5 -2
- package/src/File.mjs +5 -2
- package/src/bundle/index.cjs +6 -4
- package/src/bundle/index.mjs +6 -4
- package/src/bundle/strategies/arazzo-1/index.cjs +3 -2
- package/src/bundle/strategies/arazzo-1/index.mjs +3 -2
- package/src/bundle/strategies/asyncapi-2/index.cjs +3 -2
- package/src/bundle/strategies/asyncapi-2/index.mjs +3 -2
- package/src/bundle/strategies/openapi-2/index.cjs +3 -2
- package/src/bundle/strategies/openapi-2/index.mjs +3 -2
- package/src/bundle/strategies/openapi-3-0/index.cjs +3 -2
- package/src/bundle/strategies/openapi-3-0/index.mjs +3 -2
- package/src/bundle/strategies/openapi-3-1/index.cjs +3 -2
- package/src/bundle/strategies/openapi-3-1/index.mjs +3 -2
- package/src/dereference/index.cjs +8 -5
- package/src/dereference/index.mjs +8 -5
- package/src/dereference/strategies/DereferenceVisitor.cjs +588 -0
- package/src/dereference/strategies/DereferenceVisitor.mjs +580 -0
- package/src/dereference/strategies/arazzo-1/index.cjs +14 -39
- package/src/dereference/strategies/arazzo-1/index.mjs +15 -40
- package/src/dereference/strategies/arazzo-1/visitor.cjs +207 -331
- package/src/dereference/strategies/arazzo-1/visitor.mjs +215 -338
- package/src/dereference/strategies/asyncapi-2/index.cjs +11 -39
- package/src/dereference/strategies/asyncapi-2/index.mjs +11 -39
- package/src/dereference/strategies/asyncapi-2/visitor.cjs +133 -425
- package/src/dereference/strategies/asyncapi-2/visitor.mjs +137 -429
- package/src/dereference/strategies/openapi-2/index.cjs +12 -42
- package/src/dereference/strategies/openapi-2/index.mjs +12 -42
- package/src/dereference/strategies/openapi-2/visitor.cjs +168 -532
- package/src/dereference/strategies/openapi-2/visitor.mjs +172 -536
- package/src/dereference/strategies/openapi-3-0/index.cjs +12 -42
- package/src/dereference/strategies/openapi-3-0/index.mjs +12 -42
- package/src/dereference/strategies/openapi-3-0/visitor.cjs +194 -468
- package/src/dereference/strategies/openapi-3-0/visitor.mjs +199 -473
- package/src/dereference/strategies/openapi-3-1/index.cjs +16 -42
- package/src/dereference/strategies/openapi-3-1/index.mjs +16 -42
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +350 -602
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +357 -608
- package/src/dereference/strategies/overlay-1/index.cjs +3 -2
- package/src/dereference/strategies/overlay-1/index.mjs +3 -2
- package/src/index.cjs +0 -7
- package/src/index.mjs +0 -1
- package/src/options/index.cjs +4 -0
- package/src/options/index.mjs +4 -0
- package/src/options/util.cjs +7 -1
- package/src/options/util.mjs +8 -2
- package/src/parse/index.cjs +9 -1
- package/src/parse/index.mjs +9 -1
- package/src/parse/parsers/apidom-json/index.cjs +2 -1
- package/src/parse/parsers/apidom-json/index.mjs +2 -1
- package/src/parse/parsers/arazzo-json-1/index.cjs +2 -1
- package/src/parse/parsers/arazzo-json-1/index.mjs +2 -1
- package/src/parse/parsers/arazzo-yaml-1/index.cjs +2 -1
- package/src/parse/parsers/arazzo-yaml-1/index.mjs +2 -1
- package/src/parse/parsers/asyncapi-json-2/index.cjs +2 -1
- package/src/parse/parsers/asyncapi-json-2/index.mjs +2 -1
- package/src/parse/parsers/asyncapi-yaml-2/index.cjs +2 -1
- package/src/parse/parsers/asyncapi-yaml-2/index.mjs +2 -1
- package/src/parse/parsers/json/index.cjs +2 -1
- package/src/parse/parsers/json/index.mjs +2 -1
- package/src/parse/parsers/openapi-json-2/index.cjs +2 -1
- package/src/parse/parsers/openapi-json-2/index.mjs +2 -1
- package/src/parse/parsers/openapi-json-3-0/index.cjs +2 -1
- package/src/parse/parsers/openapi-json-3-0/index.mjs +2 -1
- package/src/parse/parsers/openapi-json-3-1/index.cjs +2 -1
- package/src/parse/parsers/openapi-json-3-1/index.mjs +2 -1
- package/src/parse/parsers/openapi-yaml-2/index.cjs +2 -1
- package/src/parse/parsers/openapi-yaml-2/index.mjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-0/index.cjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-0/index.mjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-1/index.cjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-1/index.mjs +2 -1
- package/src/parse/parsers/overlay-json-1/index.cjs +2 -1
- package/src/parse/parsers/overlay-json-1/index.mjs +2 -1
- package/src/parse/parsers/overlay-yaml-1/index.cjs +2 -1
- package/src/parse/parsers/overlay-yaml-1/index.mjs +2 -1
- package/src/parse/parsers/yaml-1-2/index.cjs +2 -1
- package/src/parse/parsers/yaml-1-2/index.mjs +2 -1
- package/src/resolve/index.cjs +6 -3
- package/src/resolve/index.mjs +6 -3
- package/src/util/url.cjs +38 -8
- package/src/util/url.mjs +35 -6
- package/types/apidom-reference.d.ts +13 -9
- package/types/dereference/strategies/DereferenceVisitor.d.ts +224 -0
- package/types/dereference/strategies/arazzo-1/index.d.ts +0 -1
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +38 -38
- package/types/dereference/strategies/asyncapi-2/index.d.ts +0 -1
- package/types/dereference/strategies/asyncapi-2/visitor.d.ts +13 -29
- package/types/dereference/strategies/openapi-2/index.d.ts +0 -1
- package/types/dereference/strategies/openapi-2/visitor.d.ts +14 -29
- package/types/dereference/strategies/openapi-3-0/index.d.ts +0 -1
- package/types/dereference/strategies/openapi-3-0/visitor.d.ts +13 -28
- package/types/dereference/strategies/openapi-3-1/index.d.ts +0 -1
- package/types/dereference/strategies/openapi-3-1/visitor.d.ts +37 -33
- package/types/index.d.ts +0 -1
- package/types/util/url.d.ts +11 -0
- package/LICENSES/AFL-3.0.txt +0 -182
- package/LICENSES/BSD-3-Clause.txt +0 -26
- package/src/dereference/util.cjs +0 -33
- package/src/dereference/util.mjs +0 -27
- package/types/dereference/util.d.ts +0 -9
|
@@ -1,406 +1,255 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { none } from 'ramda';
|
|
2
2
|
import { isUndefined } from 'ramda-adjunct';
|
|
3
|
-
import { isElement, isStringElement, isObjectElement, isParseResultElement,
|
|
4
|
-
import { toValue, fixedFields
|
|
3
|
+
import { isElement, isStringElement, isObjectElement, isParseResultElement, isPrimitiveElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
4
|
+
import { toValue, fixedFields } from '@speclynx/apidom-core';
|
|
5
5
|
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
6
|
-
import { traverse,
|
|
6
|
+
import { traverse, find } from '@speclynx/apidom-traverse';
|
|
7
7
|
import { evaluate as jsonPointerEvaluate, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
|
|
8
|
-
import { isReferenceLikeElement, isPathItemElement, isReferenceElement, isSchemaElement, isOperationElement, isBooleanJSONSchemaElement, refract, refractReference,
|
|
8
|
+
import { isReferenceLikeElement, isPathItemElement, isReferenceElement, isSchemaElement, isOperationElement, isBooleanJSONSchemaElement, refract, refractReference, refractSchema } from '@speclynx/apidom-ns-openapi-3-1';
|
|
9
9
|
import { isAnchor, uriToAnchor, locate as $anchorLocate } from "./selectors/$anchor.mjs";
|
|
10
10
|
import { locate as uriLocate } from "./selectors/uri.mjs";
|
|
11
11
|
import { locate as jsonPointerLocate } from "./selectors/json-pointer.mjs";
|
|
12
|
-
import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
|
|
13
|
-
import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
|
|
14
|
-
import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
|
|
15
12
|
import EvaluationJsonSchemaUriError from "../../../errors/EvaluationJsonSchemaUriError.mjs";
|
|
13
|
+
import DereferenceVisitor from "../DereferenceVisitor.mjs";
|
|
16
14
|
import * as url from "../../../util/url.mjs";
|
|
17
|
-
import parse from "../../../parse/index.mjs";
|
|
18
|
-
import Reference from "../../../Reference.mjs";
|
|
19
15
|
import File from "../../../File.mjs";
|
|
20
|
-
import {
|
|
21
|
-
import { AncestorLineage } from "../../util.mjs";
|
|
16
|
+
import { isJSONSchemaElementWith$id, collectSchema$ids, resolveSchema$ids, resolveSchemaBaseURI, resolveSchema$refField } from "./util.mjs";
|
|
22
17
|
/**
|
|
23
|
-
*
|
|
18
|
+
* Type a Schema Object `$ref` expects its target to have.
|
|
19
|
+
*/
|
|
20
|
+
const SCHEMA_TYPE = 'schema';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Context of a source being dereferenced: the `$id`s of the Schema Objects enclosing it.
|
|
24
24
|
*/
|
|
25
|
+
|
|
25
26
|
/**
|
|
26
27
|
* @public
|
|
27
28
|
*/
|
|
28
|
-
class OpenAPI3_1DereferenceVisitor {
|
|
29
|
-
indirections;
|
|
30
|
-
reference;
|
|
31
|
-
options;
|
|
32
|
-
refractCache;
|
|
33
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Dereferences an OpenAPI 3.1 document. Reference Objects expect the type of their
|
|
32
|
+
* `referenced-element` meta, Path Item `$ref`s a Path Item, Schema Object `$ref`s a Schema Object.
|
|
33
|
+
* Link Objects get the Operation Object they point at attached, Example Objects
|
|
34
|
+
* the external value.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
class OpenAPI3_1DereferenceVisitor extends DereferenceVisitor {
|
|
34
39
|
/**
|
|
35
|
-
* Index of `$id`-bearing schemas per document, shared across
|
|
36
|
-
* of a single dereference run
|
|
40
|
+
* Index of `$id`-bearing schemas per document, shared across the visitors
|
|
41
|
+
* of a single dereference run.
|
|
37
42
|
*/
|
|
38
43
|
schema$idIndex;
|
|
39
|
-
visited;
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* `$id`s of the JSON Schema elements enclosing the traversal root within its
|
|
43
47
|
* document (outermost first). Empty when the root is the document itself; a
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
48
|
+
* visitor traversing a fragment is seeded with the `$id`s of the schemas the
|
|
49
|
+
* fragment sits under, so the `$ref`s and `$id`s within resolve against the
|
|
50
|
+
* same base as they would from the document root.
|
|
47
51
|
*/
|
|
48
52
|
ancestorSchema$ids;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Tracks element ancestors across dive-deep traversal boundaries.
|
|
52
|
-
* Used for cycle detection: if a referenced element is found in
|
|
53
|
-
* the ancestor lineage, a circular reference is detected.
|
|
54
|
-
*/
|
|
55
|
-
ancestors;
|
|
56
53
|
constructor({
|
|
57
54
|
reference,
|
|
58
55
|
options,
|
|
59
|
-
indirections = [],
|
|
60
|
-
refractCache = new WeakMap(),
|
|
61
56
|
schema$idIndex = new WeakMap(),
|
|
62
|
-
ancestors = new AncestorLineage(),
|
|
63
|
-
visited = new WeakSet(),
|
|
64
57
|
ancestorSchema$ids = []
|
|
65
58
|
}) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
super({
|
|
60
|
+
reference,
|
|
61
|
+
options
|
|
62
|
+
});
|
|
70
63
|
this.schema$idIndex = schema$idIndex;
|
|
71
|
-
this.ancestors = new AncestorLineage(...ancestors);
|
|
72
|
-
this.visited = visited;
|
|
73
64
|
this.ancestorSchema$ids = ancestorSchema$ids;
|
|
74
65
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const directAncestors = new Set(ancestorNodes.filter(isElement));
|
|
78
|
-
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);
|
|
79
|
-
return [ancestorsLineage, directAncestors];
|
|
66
|
+
get strategyName() {
|
|
67
|
+
return 'openapi-3-1';
|
|
80
68
|
}
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
createVisitor(reference, {
|
|
70
|
+
ancestorSchema$ids
|
|
71
|
+
}) {
|
|
72
|
+
return new OpenAPI3_1DereferenceVisitor({
|
|
73
|
+
reference,
|
|
74
|
+
options: this.options,
|
|
75
|
+
schema$idIndex: this.schema$idIndex,
|
|
76
|
+
ancestorSchema$ids
|
|
77
|
+
});
|
|
83
78
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A Reference Object stands for the type its reference expects: it's dereferenced as that type.
|
|
82
|
+
*/
|
|
83
|
+
isOfType(element, type) {
|
|
84
|
+
if (isReferenceElement(element)) return true;
|
|
85
|
+
return type === SCHEMA_TYPE ? isSchemaElement(element) || isBooleanJSONSchemaElement(element) : element.element === type;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A generic structure holding a `$ref` is a Reference Object to the expected type,
|
|
90
|
+
* unless the type has its own `$ref` field (Path Item Object, Schema Object).
|
|
91
|
+
*/
|
|
92
|
+
refractTo(element, type) {
|
|
93
|
+
if (type !== SCHEMA_TYPE && type !== 'pathItem' && isReferenceLikeElement(element)) {
|
|
94
|
+
const referenceElement = refractReference(element);
|
|
95
|
+
referenceElement.meta.set('referenced-element', type);
|
|
96
|
+
return referenceElement;
|
|
100
97
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
parse: {
|
|
104
|
-
...this.options.parse,
|
|
105
|
-
mediaType: 'text/plain'
|
|
106
|
-
}
|
|
98
|
+
return refract(element, {
|
|
99
|
+
element: type
|
|
107
100
|
});
|
|
101
|
+
}
|
|
108
102
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// instead of the mutable working copy. The `immutable://` reference below preserves
|
|
120
|
-
// the original tree and could be used for pointer evaluation, ensuring every resolution
|
|
121
|
-
// context always sees raw, unresolved elements and processes them with its own
|
|
122
|
-
// ancestors/indirections. The trade-off is that elements referenced by multiple
|
|
123
|
-
// documents would be resolved once per context instead of being reused.
|
|
124
|
-
const mutableReference = new Reference({
|
|
125
|
-
uri: baseURI,
|
|
126
|
-
value: this.options.dereference.immutable ? cloneDeep(parseResult) : parseResult,
|
|
127
|
-
depth: this.reference.depth + 1
|
|
128
|
-
});
|
|
129
|
-
refSet.add(mutableReference);
|
|
130
|
-
if (this.options.dereference.immutable) {
|
|
131
|
-
// register new immutable reference with original parseResult
|
|
132
|
-
const immutableReference = new Reference({
|
|
133
|
-
uri: `immutable://${baseURI}`,
|
|
134
|
-
value: parseResult,
|
|
135
|
-
depth: this.reference.depth + 1
|
|
136
|
-
});
|
|
137
|
-
refSet.add(immutableReference);
|
|
103
|
+
/**
|
|
104
|
+
* A document as Schema Object for locating schemas within it: a generic document is
|
|
105
|
+
* refracted to Schema Object once per dereference run, a semantic one is used as it is.
|
|
106
|
+
*/
|
|
107
|
+
toSchemaDocument(document) {
|
|
108
|
+
if (!isPrimitiveElement(document)) return document;
|
|
109
|
+
let byType = this.run.refracted.get(document);
|
|
110
|
+
if (byType === undefined) {
|
|
111
|
+
byType = new Map();
|
|
112
|
+
this.run.refracted.set(document, byType);
|
|
138
113
|
}
|
|
139
|
-
|
|
114
|
+
let refracted = byType.get(SCHEMA_TYPE);
|
|
115
|
+
if (refracted === undefined) {
|
|
116
|
+
refracted = refractSchema(document);
|
|
117
|
+
byType.set(SCHEMA_TYPE, refracted);
|
|
118
|
+
}
|
|
119
|
+
return refracted;
|
|
140
120
|
}
|
|
141
121
|
|
|
142
122
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
|
|
147
|
-
* For errors already wrapped by a nested visitor: prepends the current hop to the trace.
|
|
148
|
-
*
|
|
149
|
-
* Inner/intermediate visitors always throw to let the trace accumulate.
|
|
150
|
-
* Only the entry document visitor respects continueOnError (callback/swallow/throw).
|
|
123
|
+
* Indexes the `$id`-bearing schemas of an element for the run. Transcluded elements aren't
|
|
124
|
+
* indexed: they're copies of structures indexed where they're defined,
|
|
125
|
+
* and in a document dereferenced in place their shared structure would be walked once per path.
|
|
151
126
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// dereferenceApiDOM is called with a fragment (cloneShallow creates a new root identity).
|
|
165
|
-
let location;
|
|
166
|
-
const root = this.reference.value.result;
|
|
167
|
-
if (isElement(root)) {
|
|
168
|
-
traverse(root, {
|
|
169
|
-
enter: p => {
|
|
170
|
-
if (p.node === referencingElement || this.refractCache.get(p.node) === referencingElement) {
|
|
171
|
-
location = p.formatPath();
|
|
172
|
-
p.stop();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
location ??= visitorPath.formatPath();
|
|
178
|
-
const hop = {
|
|
179
|
-
uri,
|
|
180
|
-
type,
|
|
181
|
-
refFieldName,
|
|
182
|
-
refFieldValue,
|
|
183
|
-
location,
|
|
184
|
-
codeFrame
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// enrich existing error from nested visitor or create new one
|
|
188
|
-
let unresolvedError;
|
|
189
|
-
if (error instanceof UnresolvableReferenceError) {
|
|
190
|
-
// prefix relative locations for entries belonging to the referenced document
|
|
191
|
-
const refBaseURI = this.toBaseURI(refFieldValue);
|
|
192
|
-
const fragment = URIFragmentIdentifier.fromURIReference(refFieldValue);
|
|
193
|
-
if (fragment) {
|
|
194
|
-
if (refBaseURI === error.uri && error.location) {
|
|
195
|
-
error.location = fragment + error.location;
|
|
196
|
-
}
|
|
197
|
-
for (const h of error.trace) {
|
|
198
|
-
if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
|
|
127
|
+
index$ids(element) {
|
|
128
|
+
if (this.schema$idIndex.has(element)) return;
|
|
129
|
+
const entries = [];
|
|
130
|
+
traverse(element, {
|
|
131
|
+
enter: path => {
|
|
132
|
+
if (this.isTranscluded(path.node)) {
|
|
133
|
+
path.skip();
|
|
134
|
+
} else if (isJSONSchemaElementWith$id(path.node)) {
|
|
135
|
+
entries.push({
|
|
136
|
+
schema: path.node,
|
|
137
|
+
$ids: collectSchema$ids(path)
|
|
138
|
+
});
|
|
199
139
|
}
|
|
200
140
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
unresolvedError = error;
|
|
204
|
-
} else {
|
|
205
|
-
unresolvedError = new UnresolvableReferenceError(message, {
|
|
206
|
-
cause: error,
|
|
207
|
-
type,
|
|
208
|
-
uri,
|
|
209
|
-
location,
|
|
210
|
-
codeFrame,
|
|
211
|
-
refFieldName,
|
|
212
|
-
refFieldValue,
|
|
213
|
-
trace: []
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
if (!isEntryDocument || continueOnError === false) throw unresolvedError;
|
|
217
|
-
if (typeof continueOnError === 'function') continueOnError(unresolvedError);
|
|
141
|
+
});
|
|
142
|
+
this.schema$idIndex.set(element, entries);
|
|
218
143
|
}
|
|
219
144
|
async ReferenceElement(path) {
|
|
220
145
|
const referencingElement = path.node;
|
|
221
146
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
147
|
+
// the fields of a Reference Object hold no references
|
|
148
|
+
path.skip();
|
|
226
149
|
|
|
227
|
-
//
|
|
228
|
-
if (this.
|
|
229
|
-
path.skip();
|
|
150
|
+
// ignore Reference Object without a string $ref, e.g. an empty YAML value
|
|
151
|
+
if (this.isTranscluded(referencingElement) || !isStringElement(referencingElement.$ref)) {
|
|
230
152
|
return;
|
|
231
153
|
}
|
|
232
|
-
const
|
|
154
|
+
const $ref = toValue(referencingElement.$ref);
|
|
155
|
+
const retrievalURI = this.toBaseURI($ref);
|
|
233
156
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
234
157
|
const isExternalReference = !isInternalReference;
|
|
235
158
|
|
|
236
159
|
// ignore resolving internal Reference Objects
|
|
237
160
|
if (!this.options.resolve.internal && isInternalReference) {
|
|
238
|
-
// skip traversing this reference element and all it's child elements
|
|
239
|
-
path.skip();
|
|
240
161
|
return;
|
|
241
162
|
}
|
|
242
163
|
// ignore resolving external Reference Objects
|
|
243
164
|
if (!this.options.resolve.external && isExternalReference) {
|
|
244
|
-
// skip traversing this reference element and all it's child elements
|
|
245
|
-
path.skip();
|
|
246
165
|
return;
|
|
247
166
|
}
|
|
248
|
-
const $refBaseURI = url.resolve(retrievalURI,
|
|
249
|
-
const indirectionsSize = this.indirections.length;
|
|
167
|
+
const $refBaseURI = url.resolve(retrievalURI, $ref);
|
|
250
168
|
try {
|
|
251
|
-
const reference = await this.toReference(
|
|
252
|
-
this.indirections.push(referencingElement);
|
|
169
|
+
const reference = await this.toReference($ref);
|
|
253
170
|
const jsonPointer = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
254
|
-
|
|
255
171
|
// possibly non-semantic fragment
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// applying semantics to a fragment
|
|
259
|
-
const referencedElementType = referencingElement.meta.get('referenced-element');
|
|
260
|
-
if (referencedElement.element !== referencedElementType && !isReferenceElement(referencedElement)) {
|
|
261
|
-
if (this.refractCache.has(referencedElement)) {
|
|
262
|
-
referencedElement = this.refractCache.get(referencedElement);
|
|
263
|
-
} else if (isReferenceLikeElement(referencedElement)) {
|
|
264
|
-
// handling generic indirect references
|
|
265
|
-
const sourceElement = referencedElement;
|
|
266
|
-
referencedElement = refractReference(referencedElement);
|
|
267
|
-
referencedElement.meta.set('referenced-element', referencedElementType);
|
|
268
|
-
this.refractCache.set(sourceElement, referencedElement);
|
|
269
|
-
} else {
|
|
270
|
-
// handling direct references
|
|
271
|
-
const sourceElement = referencedElement;
|
|
272
|
-
referencedElement = refract(referencedElement, {
|
|
273
|
-
element: referencedElementType
|
|
274
|
-
});
|
|
275
|
-
this.refractCache.set(sourceElement, referencedElement);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
172
|
+
const referencedElement = jsonPointerEvaluate(reference.value.result, jsonPointer);
|
|
278
173
|
|
|
279
|
-
// detect direct
|
|
174
|
+
// detect direct reference
|
|
280
175
|
if (referencingElement === referencedElement) {
|
|
281
176
|
throw new ApiDOMStructuredError('Recursive Reference Object detected', {
|
|
282
|
-
$ref
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// detect maximum depth of dereferencing
|
|
287
|
-
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
288
|
-
throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
289
|
-
maxDepth: this.options.dereference.maxDepth,
|
|
290
|
-
uri: this.reference.uri
|
|
177
|
+
$ref
|
|
291
178
|
});
|
|
292
179
|
}
|
|
293
180
|
|
|
294
|
-
//
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
181
|
+
// a reference without an expected type reads its target as its own type
|
|
182
|
+
const type = toValue(referencingElement.meta.get('referenced-element')) ?? referencedElement.element;
|
|
183
|
+
await this.addTransclusion(path, {
|
|
184
|
+
source: referencedElement,
|
|
185
|
+
type,
|
|
186
|
+
reference,
|
|
187
|
+
context: {
|
|
188
|
+
ancestorSchema$ids: []
|
|
189
|
+
}
|
|
190
|
+
}, {
|
|
191
|
+
toTranscludedElement: (element, target) => {
|
|
192
|
+
// a chain of Reference Objects leading back to itself never reaches a value
|
|
193
|
+
if (isReferenceElement(target)) return element;
|
|
194
|
+
const reusingElement = element;
|
|
195
|
+
/**
|
|
196
|
+
* Creating a new version of referenced element to avoid modifying the original one.
|
|
197
|
+
*/
|
|
198
|
+
const mergedElement = cloneShallow(target);
|
|
199
|
+
// annotate fragment with info about original Reference element
|
|
200
|
+
mergedElement.meta.set('ref-fields', {
|
|
201
|
+
$ref,
|
|
202
|
+
description: toValue(reusingElement.get('description')),
|
|
203
|
+
summary: toValue(reusingElement.get('summary'))
|
|
301
204
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
205
|
+
// annotate fragment with info about origin and type
|
|
206
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
207
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
208
|
+
|
|
209
|
+
// override description and summary (outer has higher priority then inner)
|
|
210
|
+
if (isObjectElement(target) && isObjectElement(mergedElement)) {
|
|
211
|
+
const fields = fixedFields(target, {
|
|
212
|
+
indexed: true
|
|
213
|
+
});
|
|
214
|
+
if (reusingElement.hasKey('description') && Object.hasOwn(fields, 'description')) {
|
|
215
|
+
mergedElement.remove('description');
|
|
216
|
+
mergedElement.set('description', reusingElement.get('description'));
|
|
217
|
+
}
|
|
218
|
+
if (reusingElement.hasKey('summary') && Object.hasOwn(fields, 'summary')) {
|
|
219
|
+
mergedElement.remove('summary');
|
|
220
|
+
mergedElement.set('summary', reusingElement.get('summary'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return mergedElement;
|
|
224
|
+
},
|
|
225
|
+
circular: {
|
|
226
|
+
uri: $refBaseURI,
|
|
227
|
+
meta: {
|
|
305
228
|
uri: reference.uri,
|
|
306
|
-
$ref
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
path.skip();
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Dive deep into the fragment.
|
|
318
|
-
*
|
|
319
|
-
* Cases to consider:
|
|
320
|
-
* 1. We're crossing document boundary
|
|
321
|
-
* 2. Fragment is from non-entry document
|
|
322
|
-
* 3. Fragment is a Reference Object. We need to follow it to get the eventual value
|
|
323
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
324
|
-
*/
|
|
325
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
326
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
327
|
-
if ((isExternalReference || isNonEntryDocument || isReferenceElement(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
328
|
-
if (shouldDetectCircular) {
|
|
329
|
-
this.visited.add(referencedElement);
|
|
229
|
+
$ref
|
|
230
|
+
},
|
|
231
|
+
details: {
|
|
232
|
+
$ref
|
|
233
|
+
}
|
|
330
234
|
}
|
|
331
|
-
directAncestors.add(referencingElement);
|
|
332
|
-
const visitor = new OpenAPI3_1DereferenceVisitor({
|
|
333
|
-
reference,
|
|
334
|
-
indirections: [...this.indirections],
|
|
335
|
-
options: this.options,
|
|
336
|
-
refractCache: this.refractCache,
|
|
337
|
-
schema$idIndex: this.schema$idIndex,
|
|
338
|
-
visited: this.visited,
|
|
339
|
-
ancestors: ancestorsLineage
|
|
340
|
-
});
|
|
341
|
-
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
342
|
-
mutable: true,
|
|
343
|
-
skipVisited: 'skip'
|
|
344
|
-
});
|
|
345
|
-
directAncestors.delete(referencingElement);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Creating a new version of referenced element to avoid modifying the original one.
|
|
350
|
-
*/
|
|
351
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
352
|
-
// annotate fragment with info about original Reference element
|
|
353
|
-
mergedElement.meta.set('ref-fields', {
|
|
354
|
-
$ref: toValue(referencingElement.$ref),
|
|
355
|
-
// @ts-ignore
|
|
356
|
-
description: toValue(referencingElement.description),
|
|
357
|
-
// @ts-ignore
|
|
358
|
-
summary: toValue(referencingElement.summary)
|
|
359
235
|
});
|
|
360
|
-
// annotate fragment with info about origin and type
|
|
361
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
362
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
363
|
-
|
|
364
|
-
// override description and summary (outer has higher priority then inner)
|
|
365
|
-
if (isObjectElement(referencedElement) && isObjectElement(mergedElement)) {
|
|
366
|
-
const fields = fixedFields(referencedElement, {
|
|
367
|
-
indexed: true
|
|
368
|
-
});
|
|
369
|
-
if (referencingElement.hasKey('description') && Object.hasOwn(fields, 'description')) {
|
|
370
|
-
mergedElement.remove('description');
|
|
371
|
-
mergedElement.set('description', referencingElement.get('description'));
|
|
372
|
-
}
|
|
373
|
-
if (referencingElement.hasKey('summary') && Object.hasOwn(fields, 'summary')) {
|
|
374
|
-
mergedElement.remove('summary');
|
|
375
|
-
mergedElement.set('summary', referencingElement.get('summary'));
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Transclude referencing element with merged referenced element.
|
|
381
|
-
*/
|
|
382
|
-
path.replaceWith(mergedElement);
|
|
383
236
|
} catch (error) {
|
|
384
|
-
const $ref = toValue(referencingElement.$ref);
|
|
385
237
|
this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
386
|
-
} finally {
|
|
387
|
-
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
388
238
|
}
|
|
389
239
|
}
|
|
390
240
|
async PathItemElement(path) {
|
|
391
241
|
const referencingElement = path.node;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (!isStringElement(referencingElement.$ref)) {
|
|
242
|
+
if (this.isTranscluded(referencingElement)) {
|
|
243
|
+
path.skip();
|
|
395
244
|
return;
|
|
396
245
|
}
|
|
397
246
|
|
|
398
|
-
//
|
|
399
|
-
if (
|
|
400
|
-
path.skip();
|
|
247
|
+
// ignore PathItemElement without $ref field
|
|
248
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
401
249
|
return;
|
|
402
250
|
}
|
|
403
|
-
const
|
|
251
|
+
const $ref = toValue(referencingElement.$ref);
|
|
252
|
+
const retrievalURI = this.toBaseURI($ref);
|
|
404
253
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
405
254
|
const isExternalReference = !isInternalReference;
|
|
406
255
|
|
|
@@ -414,131 +263,72 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
414
263
|
// skip traversing this Path Item element but traverse all it's child elements
|
|
415
264
|
return;
|
|
416
265
|
}
|
|
417
|
-
const $refBaseURI = url.resolve(retrievalURI,
|
|
418
|
-
const indirectionsSize = this.indirections.length;
|
|
266
|
+
const $refBaseURI = url.resolve(retrievalURI, $ref);
|
|
419
267
|
try {
|
|
420
|
-
const reference = await this.toReference(
|
|
421
|
-
this.indirections.push(referencingElement);
|
|
268
|
+
const reference = await this.toReference($ref);
|
|
422
269
|
const jsonPointer = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
423
|
-
|
|
424
270
|
// possibly non-semantic referenced element
|
|
425
|
-
|
|
271
|
+
const referencedElement = jsonPointerEvaluate(reference.value.result, jsonPointer);
|
|
426
272
|
|
|
427
|
-
//
|
|
428
|
-
if (!isPathItemElement(referencedElement)) {
|
|
429
|
-
if (this.refractCache.has(referencedElement)) {
|
|
430
|
-
referencedElement = this.refractCache.get(referencedElement);
|
|
431
|
-
} else {
|
|
432
|
-
const sourceElement = referencedElement;
|
|
433
|
-
referencedElement = refractPathItem(referencedElement);
|
|
434
|
-
this.refractCache.set(sourceElement, referencedElement);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// detect direct or indirect reference
|
|
273
|
+
// detect direct reference
|
|
439
274
|
if (referencingElement === referencedElement) {
|
|
440
275
|
throw new ApiDOMStructuredError('Recursive Path Item Object reference detected', {
|
|
441
|
-
$ref
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// detect maximum depth of dereferencing
|
|
446
|
-
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
447
|
-
throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
448
|
-
maxDepth: this.options.dereference.maxDepth,
|
|
449
|
-
uri: this.reference.uri
|
|
276
|
+
$ref
|
|
450
277
|
});
|
|
451
278
|
}
|
|
279
|
+
await this.addTransclusion(path, {
|
|
280
|
+
source: referencedElement,
|
|
281
|
+
type: 'pathItem',
|
|
282
|
+
reference,
|
|
283
|
+
context: {
|
|
284
|
+
ancestorSchema$ids: []
|
|
285
|
+
}
|
|
286
|
+
}, {
|
|
287
|
+
toTranscludedElement: (element, target) => {
|
|
288
|
+
if (!isPathItemElement(target)) return target;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
|
|
292
|
+
*/
|
|
293
|
+
const mergedElement = cloneShallow(target);
|
|
294
|
+
// existing keywords from referencing PathItemElement overrides ones from referenced element
|
|
295
|
+
element.forEach((value, keyElement, item) => {
|
|
296
|
+
mergedElement.remove(toValue(keyElement));
|
|
297
|
+
mergedElement.content.push(item);
|
|
298
|
+
});
|
|
299
|
+
mergedElement.remove('$ref');
|
|
452
300
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
reference.refSet.circular = true;
|
|
457
|
-
if (this.options.dereference.circular === 'error') {
|
|
458
|
-
throw new ApiDOMStructuredError('Circular reference detected', {
|
|
459
|
-
$ref: toValue(referencingElement.$ref)
|
|
301
|
+
// annotate referenced element with info about original referencing element
|
|
302
|
+
mergedElement.meta.set('ref-fields', {
|
|
303
|
+
$ref
|
|
460
304
|
});
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
305
|
+
// annotate referenced element with info about origin and type
|
|
306
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
307
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
308
|
+
return mergedElement;
|
|
309
|
+
},
|
|
310
|
+
aliasField: '$ref',
|
|
311
|
+
circular: {
|
|
312
|
+
uri: $refBaseURI,
|
|
313
|
+
meta: {
|
|
464
314
|
uri: reference.uri,
|
|
465
|
-
$ref
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
path.skip();
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Dive deep into the fragment.
|
|
477
|
-
*
|
|
478
|
-
* Cases to consider:
|
|
479
|
-
* 1. We're crossing document boundary
|
|
480
|
-
* 2. Fragment is from non-entry document
|
|
481
|
-
* 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
|
|
482
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
483
|
-
*/
|
|
484
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
485
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
486
|
-
if ((isExternalReference || isNonEntryDocument || isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
487
|
-
if (shouldDetectCircular) {
|
|
488
|
-
this.visited.add(referencedElement);
|
|
315
|
+
$ref
|
|
316
|
+
},
|
|
317
|
+
details: {
|
|
318
|
+
$ref
|
|
319
|
+
}
|
|
489
320
|
}
|
|
490
|
-
|
|
491
|
-
const visitor = new OpenAPI3_1DereferenceVisitor({
|
|
492
|
-
reference,
|
|
493
|
-
indirections: [...this.indirections],
|
|
494
|
-
options: this.options,
|
|
495
|
-
refractCache: this.refractCache,
|
|
496
|
-
schema$idIndex: this.schema$idIndex,
|
|
497
|
-
visited: this.visited,
|
|
498
|
-
ancestors: ancestorsLineage
|
|
499
|
-
});
|
|
500
|
-
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
501
|
-
mutable: true,
|
|
502
|
-
skipVisited: 'skip'
|
|
503
|
-
});
|
|
504
|
-
directAncestors.delete(referencingElement);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
|
|
509
|
-
*/
|
|
510
|
-
if (isPathItemElement(referencedElement)) {
|
|
511
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
512
|
-
// existing keywords from referencing PathItemElement overrides ones from referenced element
|
|
513
|
-
referencingElement.forEach((value, keyElement, item) => {
|
|
514
|
-
mergedElement.remove(toValue(keyElement));
|
|
515
|
-
mergedElement.content.push(item);
|
|
516
|
-
});
|
|
517
|
-
mergedElement.remove('$ref');
|
|
518
|
-
|
|
519
|
-
// annotate referenced element with info about original referencing element
|
|
520
|
-
mergedElement.meta.set('ref-fields', {
|
|
521
|
-
$ref: toValue(referencingElement.$ref)
|
|
522
|
-
});
|
|
523
|
-
// annotate referenced element with info about origin and type
|
|
524
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
525
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
526
|
-
referencedElement = mergedElement;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Transclude referencing element with merged referenced element.
|
|
531
|
-
*/
|
|
532
|
-
path.replaceWith(referencedElement);
|
|
321
|
+
});
|
|
533
322
|
} catch (error) {
|
|
534
|
-
const $ref = toValue(referencingElement.$ref);
|
|
535
323
|
this.handleError(`Error while dereferencing Path Item Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
536
|
-
} finally {
|
|
537
|
-
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
538
324
|
}
|
|
539
325
|
}
|
|
540
326
|
async LinkElement(path) {
|
|
541
327
|
const linkElement = path.node;
|
|
328
|
+
if (this.isTranscluded(linkElement)) {
|
|
329
|
+
path.skip();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
542
332
|
|
|
543
333
|
// ignore LinkElement without operationRef or operationId field
|
|
544
334
|
if (!isStringElement(linkElement.operationRef) && !isStringElement(linkElement.operationId)) {
|
|
@@ -553,11 +343,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
553
343
|
});
|
|
554
344
|
}
|
|
555
345
|
try {
|
|
556
|
-
let operationElement;
|
|
557
346
|
if (isStringElement(linkElement.operationRef)) {
|
|
347
|
+
const operationRef = toValue(linkElement.operationRef);
|
|
558
348
|
// possibly non-semantic referenced element
|
|
559
|
-
const jsonPointer = URIFragmentIdentifier.fromURIReference(
|
|
560
|
-
const retrievalURI = this.toBaseURI(
|
|
349
|
+
const jsonPointer = URIFragmentIdentifier.fromURIReference(operationRef);
|
|
350
|
+
const retrievalURI = this.toBaseURI(operationRef);
|
|
561
351
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
562
352
|
const isExternalReference = !isInternalReference;
|
|
563
353
|
|
|
@@ -571,51 +361,56 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
571
361
|
// skip traversing this Link element but traverse all it's child elements
|
|
572
362
|
return;
|
|
573
363
|
}
|
|
574
|
-
const reference = await this.toReference(
|
|
575
|
-
operationElement = jsonPointerEvaluate(reference.value.result, jsonPointer);
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
operationElement = refractOperation(operationElement);
|
|
583
|
-
this.refractCache.set(sourceElement, operationElement);
|
|
364
|
+
const reference = await this.toReference(operationRef);
|
|
365
|
+
const operationElement = jsonPointerEvaluate(reference.value.result, jsonPointer);
|
|
366
|
+
await this.addAttachment(path, {
|
|
367
|
+
source: operationElement,
|
|
368
|
+
type: 'operation',
|
|
369
|
+
reference,
|
|
370
|
+
context: {
|
|
371
|
+
ancestorSchema$ids: []
|
|
584
372
|
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
373
|
+
}, (element, target) => {
|
|
374
|
+
// create shallow clone to be able to annotate with metadata
|
|
375
|
+
const operationCopy = cloneShallow(target);
|
|
376
|
+
// annotate operation element with info about origin and type
|
|
377
|
+
operationCopy.meta.set('ref-origin', reference.uri);
|
|
378
|
+
operationCopy.meta.set('ref-type', element.element);
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Transclude Link Object containing Operation Object in its meta.
|
|
382
|
+
*/
|
|
383
|
+
const linkElementCopy = cloneShallow(element);
|
|
384
|
+
linkElementCopy.operationRef?.meta.set('operation', operationCopy);
|
|
385
|
+
return linkElementCopy;
|
|
386
|
+
});
|
|
598
387
|
return;
|
|
599
388
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
389
|
+
const operationId = toValue(linkElement.operationId);
|
|
390
|
+
const reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
391
|
+
const operationPath = find(reference.value.result, p => isOperationElement(p.node) && isElement(p.node.operationId) && p.node.operationId.equals(operationId));
|
|
392
|
+
const operationElement = operationPath?.node;
|
|
393
|
+
// OperationElement not found by its operationId
|
|
394
|
+
if (isUndefined(operationElement)) {
|
|
395
|
+
throw new ApiDOMStructuredError(`OperationElement(operationId=${operationId}) not found`, {
|
|
396
|
+
operationId
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
await this.addAttachment(path, {
|
|
400
|
+
source: operationElement,
|
|
401
|
+
type: 'operation',
|
|
402
|
+
reference,
|
|
403
|
+
context: {
|
|
404
|
+
ancestorSchema$ids: []
|
|
610
405
|
}
|
|
611
|
-
|
|
612
|
-
linkElementCopy.operationId?.meta.set('operation', operationElement);
|
|
613
|
-
|
|
406
|
+
}, (element, target) => {
|
|
614
407
|
/**
|
|
615
408
|
* Transclude Link Object containing Operation Object in its meta.
|
|
616
409
|
*/
|
|
617
|
-
|
|
618
|
-
|
|
410
|
+
const linkElementCopy = cloneShallow(element);
|
|
411
|
+
linkElementCopy.operationId?.meta.set('operation', target);
|
|
412
|
+
return linkElementCopy;
|
|
413
|
+
});
|
|
619
414
|
} catch (error) {
|
|
620
415
|
const refFieldName = isStringElement(linkElement.operationRef) ? 'operationRef' : 'operationId';
|
|
621
416
|
const refFieldValue = isStringElement(linkElement.operationRef) ? toValue(linkElement.operationRef) : toValue(linkElement.operationId);
|
|
@@ -624,6 +419,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
624
419
|
}
|
|
625
420
|
async ExampleElement(path) {
|
|
626
421
|
const exampleElement = path.node;
|
|
422
|
+
if (this.isTranscluded(exampleElement)) {
|
|
423
|
+
path.skip();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
627
426
|
|
|
628
427
|
// ignore ExampleElement without externalValue field
|
|
629
428
|
if (!isStringElement(exampleElement.externalValue)) {
|
|
@@ -637,7 +436,8 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
637
436
|
externalValue: toValue(exampleElement.externalValue)
|
|
638
437
|
});
|
|
639
438
|
}
|
|
640
|
-
const
|
|
439
|
+
const externalValue = toValue(exampleElement.externalValue);
|
|
440
|
+
const retrievalURI = this.toBaseURI(externalValue);
|
|
641
441
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
642
442
|
const isExternalReference = !isInternalReference;
|
|
643
443
|
|
|
@@ -652,39 +452,44 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
652
452
|
return;
|
|
653
453
|
}
|
|
654
454
|
try {
|
|
655
|
-
const reference = await this.toReference(
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
455
|
+
const reference = await this.toReference(externalValue);
|
|
456
|
+
const valueSource = reference.value.result;
|
|
457
|
+
await this.addAttachment(path, {
|
|
458
|
+
source: valueSource,
|
|
459
|
+
type: valueSource.element,
|
|
460
|
+
reference,
|
|
461
|
+
context: {
|
|
462
|
+
ancestorSchema$ids: []
|
|
463
|
+
}
|
|
464
|
+
}, (element, target) => {
|
|
465
|
+
// shallow clone of the referenced element
|
|
466
|
+
const valueElement = cloneShallow(target);
|
|
467
|
+
// annotate element with info about origin and type
|
|
468
|
+
valueElement.meta.set('ref-origin', reference.uri);
|
|
469
|
+
valueElement.meta.set('ref-type', element.element);
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Transclude Example Object containing external value.
|
|
473
|
+
*/
|
|
474
|
+
const exampleElementCopy = cloneShallow(element);
|
|
475
|
+
exampleElementCopy.value = valueElement;
|
|
476
|
+
return exampleElementCopy;
|
|
477
|
+
});
|
|
669
478
|
} catch (error) {
|
|
670
|
-
const externalValue = toValue(exampleElement.externalValue);
|
|
671
479
|
this.handleError(`Error while dereferencing Example Object. Cannot resolve externalValue "${externalValue}": ${error.message}`, error, exampleElement, 'externalValue', externalValue, path);
|
|
672
480
|
}
|
|
673
481
|
}
|
|
674
482
|
async SchemaElement(path) {
|
|
675
483
|
const referencingElement = path.node;
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (!isStringElement(referencingElement.$ref)) {
|
|
484
|
+
if (this.isTranscluded(referencingElement)) {
|
|
485
|
+
path.skip();
|
|
679
486
|
return;
|
|
680
487
|
}
|
|
681
488
|
|
|
682
|
-
// skip current referencing
|
|
683
|
-
if (
|
|
684
|
-
path.skip();
|
|
489
|
+
// skip current referencing schema as $ref keyword was not defined
|
|
490
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
685
491
|
return;
|
|
686
492
|
}
|
|
687
|
-
const indirectionsSize = this.indirections.length;
|
|
688
493
|
try {
|
|
689
494
|
// compute baseURI using rules around $id and $ref keywords
|
|
690
495
|
let reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
@@ -727,10 +532,12 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
727
532
|
try {
|
|
728
533
|
// the fragment's $ids resolve against the base its enclosing chain
|
|
729
534
|
// leaves in effect, which then also prefixes the located schema's chain
|
|
535
|
+
const fragmentAsSchema = this.toSchemaDocument(fragmentElement);
|
|
536
|
+
this.index$ids(fragmentAsSchema);
|
|
730
537
|
({
|
|
731
538
|
element: referencedElement,
|
|
732
539
|
ancestorSchema$ids
|
|
733
|
-
} = uriLocate(selector,
|
|
540
|
+
} = uriLocate(selector, fragmentAsSchema, {
|
|
734
541
|
baseURI: rootBaseURI,
|
|
735
542
|
index: this.schema$idIndex
|
|
736
543
|
}));
|
|
@@ -741,15 +548,16 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
741
548
|
}
|
|
742
549
|
// the document index is rooted at the document, so the located chain
|
|
743
550
|
// is already document-relative and takes no prefix
|
|
551
|
+
const documentAsSchema = this.toSchemaDocument(documentElement);
|
|
552
|
+
this.index$ids(documentAsSchema);
|
|
744
553
|
({
|
|
745
554
|
element: referencedElement,
|
|
746
555
|
ancestorSchema$ids
|
|
747
|
-
} = uriLocate(selector,
|
|
556
|
+
} = uriLocate(selector, documentAsSchema, {
|
|
748
557
|
baseURI: reference.uri,
|
|
749
558
|
index: this.schema$idIndex
|
|
750
559
|
}));
|
|
751
560
|
}
|
|
752
|
-
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
753
561
|
|
|
754
562
|
// ignore resolving internal Schema Objects
|
|
755
563
|
if (!this.options.resolve.internal && isInternalReference) {
|
|
@@ -779,12 +587,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
779
587
|
}
|
|
780
588
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
781
589
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
782
|
-
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
|
|
783
590
|
({
|
|
784
591
|
element: referencedElement,
|
|
785
592
|
ancestorSchema$ids
|
|
786
|
-
} = jsonPointerLocate(
|
|
787
|
-
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
593
|
+
} = jsonPointerLocate(this.toSchemaDocument(reference.value.result), selector));
|
|
788
594
|
}
|
|
789
595
|
} catch (error) {
|
|
790
596
|
/**
|
|
@@ -809,12 +615,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
809
615
|
}
|
|
810
616
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
811
617
|
const selector = uriToAnchor($refBaseURI);
|
|
812
|
-
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
|
|
813
618
|
({
|
|
814
619
|
element: referencedElement,
|
|
815
620
|
ancestorSchema$ids
|
|
816
|
-
} = $anchorLocate(selector,
|
|
817
|
-
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
621
|
+
} = $anchorLocate(selector, this.toSchemaDocument(reference.value.result)));
|
|
818
622
|
} else {
|
|
819
623
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
820
624
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
@@ -833,137 +637,82 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
833
637
|
}
|
|
834
638
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
835
639
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
836
|
-
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
|
|
837
640
|
({
|
|
838
641
|
element: referencedElement,
|
|
839
642
|
ancestorSchema$ids
|
|
840
|
-
} = jsonPointerLocate(
|
|
841
|
-
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
643
|
+
} = jsonPointerLocate(this.toSchemaDocument(reference.value.result), selector));
|
|
842
644
|
}
|
|
843
645
|
} else {
|
|
844
646
|
throw error;
|
|
845
647
|
}
|
|
846
648
|
}
|
|
847
|
-
|
|
649
|
+
const $ref = toValue(referencingElement.$ref);
|
|
848
650
|
|
|
849
|
-
// detect direct
|
|
651
|
+
// detect direct reference
|
|
850
652
|
if (referencingElement === referencedElement) {
|
|
851
653
|
throw new ApiDOMStructuredError('Recursive Schema Object reference detected', {
|
|
852
|
-
$ref
|
|
654
|
+
$ref
|
|
853
655
|
});
|
|
854
656
|
}
|
|
855
|
-
|
|
856
|
-
//
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
657
|
+
const origin = reference.uri;
|
|
658
|
+
// a Schema Object $ref reads whatever it points at as a Schema Object
|
|
659
|
+
await this.addTransclusion(path, {
|
|
660
|
+
source: referencedElement,
|
|
661
|
+
type: SCHEMA_TYPE,
|
|
662
|
+
reference,
|
|
663
|
+
context: {
|
|
664
|
+
ancestorSchema$ids
|
|
665
|
+
}
|
|
666
|
+
}, {
|
|
667
|
+
toTranscludedElement: (element, target) => {
|
|
668
|
+
// Boolean JSON Schemas
|
|
669
|
+
if (isBooleanJSONSchemaElement(target)) {
|
|
670
|
+
const booleanJsonSchemaElement = cloneDeep(target);
|
|
671
|
+
// annotate referenced element with info about original referencing element
|
|
672
|
+
booleanJsonSchemaElement.meta.set('ref-fields', {
|
|
673
|
+
$ref
|
|
674
|
+
});
|
|
675
|
+
// annotate referenced element with info about origin and type
|
|
676
|
+
booleanJsonSchemaElement.meta.set('ref-origin', origin);
|
|
677
|
+
booleanJsonSchemaElement.meta.set('ref-type', element.element);
|
|
678
|
+
return booleanJsonSchemaElement;
|
|
679
|
+
}
|
|
680
|
+
if (!isSchemaElement(target)) return target;
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
|
|
684
|
+
*/
|
|
685
|
+
const mergedElement = cloneShallow(target);
|
|
686
|
+
// existing keywords from referencing schema overrides ones from referenced schema
|
|
687
|
+
element.forEach((value, keyElement, item) => {
|
|
688
|
+
mergedElement.remove(toValue(keyElement));
|
|
689
|
+
mergedElement.content.push(item);
|
|
871
690
|
});
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
$ref: toValue(referencingElement.$ref)
|
|
691
|
+
mergedElement.remove('$ref');
|
|
692
|
+
// annotate referenced element with info about original referencing element
|
|
693
|
+
mergedElement.meta.set('ref-fields', {
|
|
694
|
+
$ref
|
|
877
695
|
});
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
894
|
-
*/
|
|
895
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
896
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
897
|
-
// content transplanted into the referencing site is traversed under that
|
|
898
|
-
// site's $id chain; a fragment enclosed by a different chain has its own
|
|
899
|
-
// $refs resolved in place first, by a nested visitor seeded with that chain
|
|
900
|
-
const hasDifferentEnclosingBase = resolveSchema$ids(reference.uri, ancestorSchema$ids) !== resolveSchema$ids(rootBaseURI, schema$idsOf(path.getAncestorNodes().reverse()));
|
|
901
|
-
if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
902
|
-
if (shouldDetectCircular) {
|
|
903
|
-
this.visited.add(referencedElement);
|
|
696
|
+
// annotate fragment with info about origin and type
|
|
697
|
+
mergedElement.meta.set('ref-origin', origin);
|
|
698
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
699
|
+
return mergedElement;
|
|
700
|
+
},
|
|
701
|
+
aliasField: '$ref',
|
|
702
|
+
circular: {
|
|
703
|
+
uri: $refBaseURI,
|
|
704
|
+
meta: {
|
|
705
|
+
uri: origin,
|
|
706
|
+
$ref
|
|
707
|
+
},
|
|
708
|
+
details: {
|
|
709
|
+
$ref
|
|
710
|
+
}
|
|
904
711
|
}
|
|
905
|
-
|
|
906
|
-
const visitor = new OpenAPI3_1DereferenceVisitor({
|
|
907
|
-
reference,
|
|
908
|
-
indirections: [...this.indirections],
|
|
909
|
-
options: this.options,
|
|
910
|
-
refractCache: this.refractCache,
|
|
911
|
-
schema$idIndex: this.schema$idIndex,
|
|
912
|
-
visited: this.visited,
|
|
913
|
-
ancestors: ancestorsLineage,
|
|
914
|
-
ancestorSchema$ids
|
|
915
|
-
});
|
|
916
|
-
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
917
|
-
mutable: true,
|
|
918
|
-
skipVisited: 'skip'
|
|
919
|
-
});
|
|
920
|
-
directAncestors.delete(referencingElement);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// Boolean JSON Schemas
|
|
924
|
-
if (isBooleanJSONSchemaElement(referencedElement)) {
|
|
925
|
-
const booleanJsonSchemaElement = cloneDeep(referencedElement);
|
|
926
|
-
// annotate referenced element with info about original referencing element
|
|
927
|
-
booleanJsonSchemaElement.meta.set('ref-fields', {
|
|
928
|
-
$ref: toValue(referencingElement.$ref)
|
|
929
|
-
});
|
|
930
|
-
// annotate referenced element with info about origin and type
|
|
931
|
-
booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
|
|
932
|
-
booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
|
|
933
|
-
path.replaceWith(booleanJsonSchemaElement);
|
|
934
|
-
path.skip();
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
/**
|
|
939
|
-
* Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
|
|
940
|
-
*/
|
|
941
|
-
if (isSchemaElement(referencedElement)) {
|
|
942
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
943
|
-
// existing keywords from referencing schema overrides ones from referenced schema
|
|
944
|
-
referencingElement.forEach((value, keyElement, item) => {
|
|
945
|
-
mergedElement.remove(toValue(keyElement));
|
|
946
|
-
mergedElement.content.push(item);
|
|
947
|
-
});
|
|
948
|
-
mergedElement.remove('$ref');
|
|
949
|
-
// annotate referenced element with info about original referencing element
|
|
950
|
-
mergedElement.meta.set('ref-fields', {
|
|
951
|
-
$ref: toValue(referencingElement.$ref)
|
|
952
|
-
});
|
|
953
|
-
// annotate fragment with info about origin and type
|
|
954
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
955
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
956
|
-
referencedElement = mergedElement;
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Transclude referencing element with merged referenced element.
|
|
960
|
-
*/
|
|
961
|
-
path.replaceWith(referencedElement);
|
|
712
|
+
});
|
|
962
713
|
} catch (error) {
|
|
963
714
|
const $ref = toValue(referencingElement.$ref);
|
|
964
715
|
this.handleError(`Error while dereferencing Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
965
|
-
} finally {
|
|
966
|
-
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
967
716
|
}
|
|
968
717
|
}
|
|
969
718
|
}
|