@speclynx/apidom-reference 6.2.0 → 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 +49 -0
- package/README.md +45 -0
- package/dist/apidom-reference.browser.js +4701 -3478
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +29 -28
- package/src/File.cjs +5 -2
- package/src/File.mjs +5 -2
- 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/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/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/types/apidom-reference.d.ts +0 -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/src/dereference/util.cjs +0 -33
- package/src/dereference/util.mjs +0 -27
- package/types/dereference/util.d.ts +0 -9
|
@@ -1,249 +1,171 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { toValue
|
|
1
|
+
import { none } from 'ramda';
|
|
2
|
+
import { isStringElement, isParseResultElement, isPrimitiveElement, includesClasses, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
3
|
+
import { toValue } from '@speclynx/apidom-core';
|
|
4
4
|
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
5
|
-
import { traverse
|
|
5
|
+
import { traverse } from '@speclynx/apidom-traverse';
|
|
6
6
|
import { evaluate as jsonPointerEvaluate, compile as jsonPointerCompile, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
|
|
7
|
-
import { isParameterElement, isJSONSchemaElement, isBooleanJSONSchemaElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
7
|
+
import { isParameterElement, isJSONSchemaElement, isBooleanJSONSchemaElement, refract, refractJSONSchema } from '@speclynx/apidom-ns-arazzo-1';
|
|
8
8
|
import { parse as parseRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
|
|
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 "../openapi-3-1/selectors/json-pointer.mjs";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
|
|
16
|
-
import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
|
|
12
|
+
import { isJSONSchemaElementWith$id, collectSchema$ids, resolveSchema$ids, resolveSchemaBaseURI, resolveSchema$refField } from "../openapi-3-1/util.mjs";
|
|
13
|
+
import { resolveArazzo$selfField, identifiedBy$self } from "./util.mjs";
|
|
14
|
+
import DereferenceVisitor from "../DereferenceVisitor.mjs";
|
|
17
15
|
import * as url from "../../../util/url.mjs";
|
|
18
|
-
import parse from "../../../parse/index.mjs";
|
|
19
|
-
import Reference from "../../../Reference.mjs";
|
|
20
16
|
import File from "../../../File.mjs";
|
|
21
|
-
import { AncestorLineage } from "../../util.mjs";
|
|
22
17
|
import EvaluationJsonSchemaUriError from "../../../errors/EvaluationJsonSchemaUriError.mjs";
|
|
23
18
|
/**
|
|
24
|
-
*
|
|
19
|
+
* Type a JSON Schema `$ref` expects its target to have.
|
|
25
20
|
*/
|
|
21
|
+
const JSON_SCHEMA_TYPE = 'JSONSchema';
|
|
22
|
+
|
|
26
23
|
/**
|
|
27
|
-
*
|
|
24
|
+
* Context of a source being dereferenced: the `$id`s of the JSON Schema elements enclosing it.
|
|
28
25
|
*/
|
|
29
|
-
class Arazzo1DereferenceVisitor {
|
|
30
|
-
indirections;
|
|
31
|
-
reference;
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* to the retrieval URI (`this.reference.uri`).
|
|
37
|
-
*/
|
|
38
|
-
baseURI;
|
|
39
|
-
options;
|
|
27
|
+
/**
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
40
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Dereferences an Arazzo document. Reusable Objects expect the type of their
|
|
33
|
+
* `referenced-element` meta, JSON Schema `$ref`s expect a JSON Schema.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
class Arazzo1DereferenceVisitor extends DereferenceVisitor {
|
|
41
38
|
/**
|
|
42
|
-
* Index of `$id`-bearing schemas per document, shared across
|
|
43
|
-
* of a single dereference run
|
|
39
|
+
* Index of `$id`-bearing schemas per document, shared across the visitors
|
|
40
|
+
* of a single dereference run.
|
|
44
41
|
*/
|
|
45
42
|
schema$idIndex;
|
|
46
|
-
visited;
|
|
47
43
|
|
|
48
44
|
/**
|
|
49
45
|
* `$id`s of the JSON Schema elements enclosing the traversal root within its
|
|
50
46
|
* document (outermost first). Empty when the root is the document itself; a
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
47
|
+
* visitor traversing a fragment is seeded with the `$id`s of the schemas the
|
|
48
|
+
* fragment sits under, so the `$ref`s and `$id`s within resolve against the
|
|
49
|
+
* same base as they would from the document root.
|
|
54
50
|
*/
|
|
55
51
|
ancestorSchema$ids;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Tracks element ancestors across dive-deep traversal boundaries.
|
|
59
|
-
* Used for cycle detection: if a referenced element is found in
|
|
60
|
-
* the ancestor lineage, a circular reference is detected.
|
|
61
|
-
*/
|
|
62
|
-
ancestors;
|
|
63
52
|
constructor({
|
|
64
53
|
reference,
|
|
65
54
|
options,
|
|
66
|
-
indirections = [],
|
|
67
55
|
schema$idIndex = new WeakMap(),
|
|
68
|
-
ancestors = new AncestorLineage(),
|
|
69
|
-
visited = new WeakSet(),
|
|
70
56
|
ancestorSchema$ids = []
|
|
71
57
|
}) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
super({
|
|
59
|
+
reference,
|
|
60
|
+
options
|
|
61
|
+
});
|
|
76
62
|
this.schema$idIndex = schema$idIndex;
|
|
77
|
-
this.ancestors = new AncestorLineage(...ancestors);
|
|
78
|
-
this.visited = visited;
|
|
79
63
|
this.ancestorSchema$ids = ancestorSchema$ids;
|
|
80
64
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const directAncestors = new Set(ancestorNodes.filter(isElement));
|
|
84
|
-
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);
|
|
85
|
-
return [ancestorsLineage, directAncestors];
|
|
65
|
+
get strategyName() {
|
|
66
|
+
return 'arazzo-1';
|
|
86
67
|
}
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
createVisitor(reference, {
|
|
69
|
+
ancestorSchema$ids
|
|
70
|
+
}) {
|
|
71
|
+
return new Arazzo1DereferenceVisitor({
|
|
72
|
+
reference,
|
|
73
|
+
options: this.options,
|
|
74
|
+
schema$idIndex: this.schema$idIndex,
|
|
75
|
+
ancestorSchema$ids
|
|
76
|
+
});
|
|
89
77
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
|
|
94
|
-
maxDepth: this.options.resolve.maxDepth,
|
|
95
|
-
uri: this.reference.uri
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
const baseURI = this.toBaseURI(uri);
|
|
99
|
-
const {
|
|
100
|
-
refSet
|
|
101
|
-
} = this.reference;
|
|
102
|
-
|
|
103
|
-
// we've already processed this Reference in past
|
|
104
|
-
if (refSet.has(baseURI)) {
|
|
105
|
-
return refSet.find(propEq(baseURI, 'uri'));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// identity-based referencing: URI matches `$self` of an already processed Arazzo document
|
|
109
|
-
const referenceBy$self = refSet.find(identifiedBy$self(baseURI));
|
|
110
|
-
if (referenceBy$self !== undefined) {
|
|
111
|
-
return referenceBy$self;
|
|
112
|
-
}
|
|
113
|
-
const parseResult = await parse(url.unsanitize(baseURI), {
|
|
114
|
-
...this.options,
|
|
115
|
-
parse: {
|
|
116
|
-
...this.options.parse,
|
|
117
|
-
mediaType: 'text/plain'
|
|
118
|
-
}
|
|
78
|
+
refractTo(element, type) {
|
|
79
|
+
return type === JSON_SCHEMA_TYPE ? refractJSONSchema(element) : refract(element, {
|
|
80
|
+
element: type
|
|
119
81
|
});
|
|
82
|
+
}
|
|
83
|
+
isOfType(element, type) {
|
|
84
|
+
return type === JSON_SCHEMA_TYPE ? isJSONSchemaElement(element) || isBooleanJSONSchemaElement(element) : element.element === type;
|
|
85
|
+
}
|
|
120
86
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// $ref. That resolved element was produced using the entry document's resolution context
|
|
127
|
-
// (ancestors, indirections), which may differ from the external document's context.
|
|
128
|
-
// This can affect cycle detection in rare cross-document circular reference patterns.
|
|
129
|
-
//
|
|
130
|
-
// Remediation: evaluate JSON pointers against the immutable (original) parse tree
|
|
131
|
-
// instead of the mutable working copy. The `immutable://` reference below preserves
|
|
132
|
-
// the original tree and could be used for pointer evaluation, ensuring every resolution
|
|
133
|
-
// context always sees raw, unresolved elements and processes them with its own
|
|
134
|
-
// ancestors/indirections. The trade-off is that elements referenced by multiple
|
|
135
|
-
// documents would be resolved once per context instead of being reused.
|
|
136
|
-
const mutableReference = new Reference({
|
|
137
|
-
uri: baseURI,
|
|
138
|
-
value: this.options.dereference.immutable ? cloneDeep(parseResult) : parseResult,
|
|
139
|
-
depth: this.reference.depth + 1
|
|
140
|
-
});
|
|
141
|
-
refSet.add(mutableReference);
|
|
142
|
-
if (this.options.dereference.immutable) {
|
|
143
|
-
// register new immutable reference with a refSet
|
|
144
|
-
const immutableReference = new Reference({
|
|
145
|
-
uri: `immutable://${baseURI}`,
|
|
146
|
-
value: parseResult,
|
|
147
|
-
depth: this.reference.depth + 1
|
|
148
|
-
});
|
|
149
|
-
refSet.add(immutableReference);
|
|
150
|
-
}
|
|
151
|
-
return mutableReference;
|
|
87
|
+
/**
|
|
88
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back to the retrieval URI.
|
|
89
|
+
*/
|
|
90
|
+
toDocumentBaseURI(reference) {
|
|
91
|
+
return resolveArazzo$selfField(reference.uri, reference.value?.result);
|
|
152
92
|
}
|
|
153
93
|
|
|
154
94
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* For new errors: wraps in UnresolvableReferenceError with structured context
|
|
158
|
-
* (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
|
|
159
|
-
* For errors already wrapped by a nested visitor: prepends the current hop to the trace.
|
|
160
|
-
*
|
|
161
|
-
* Inner/intermediate visitors always throw to let the trace accumulate.
|
|
162
|
-
* Only the entry document visitor respects continueOnError (callback/swallow/throw).
|
|
95
|
+
* Identity-based referencing: URI matches `$self` of an already processed Arazzo document.
|
|
163
96
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} = this.options.dereference;
|
|
168
|
-
const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
|
|
169
|
-
const uri = this.reference.uri;
|
|
170
|
-
const type = referencingElement.element;
|
|
171
|
-
const codeFrame = toYAML(referencingElement);
|
|
97
|
+
findReferenceByIdentity(uri) {
|
|
98
|
+
return this.reference.refSet?.find(identifiedBy$self(uri));
|
|
99
|
+
}
|
|
172
100
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
101
|
+
/**
|
|
102
|
+
* A document as JSON Schema for locating schemas within it: a generic document is
|
|
103
|
+
* refracted to JSON Schema once per dereference run, a semantic one is used as it is.
|
|
104
|
+
*/
|
|
105
|
+
toJSONSchemaDocument(document) {
|
|
106
|
+
if (!isPrimitiveElement(document)) return document;
|
|
107
|
+
let byType = this.run.refracted.get(document);
|
|
108
|
+
if (byType === undefined) {
|
|
109
|
+
byType = new Map();
|
|
110
|
+
this.run.refracted.set(document, byType);
|
|
111
|
+
}
|
|
112
|
+
let refracted = byType.get(JSON_SCHEMA_TYPE);
|
|
113
|
+
if (refracted === undefined) {
|
|
114
|
+
refracted = refractJSONSchema(document);
|
|
115
|
+
byType.set(JSON_SCHEMA_TYPE, refracted);
|
|
188
116
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
uri,
|
|
192
|
-
type,
|
|
193
|
-
refFieldName,
|
|
194
|
-
refFieldValue,
|
|
195
|
-
location,
|
|
196
|
-
codeFrame
|
|
197
|
-
};
|
|
117
|
+
return refracted;
|
|
118
|
+
}
|
|
198
119
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Indexes the `$id`-bearing schemas of a document for the run. Transcluded elements aren't
|
|
122
|
+
* indexed: they're copies of structures indexed where they're defined,
|
|
123
|
+
* and in a document dereferenced in place their shared structure would be walked once per path.
|
|
124
|
+
*/
|
|
125
|
+
index$ids(element) {
|
|
126
|
+
if (this.schema$idIndex.has(element)) return;
|
|
127
|
+
const entries = [];
|
|
128
|
+
traverse(element, {
|
|
129
|
+
enter: path => {
|
|
130
|
+
if (this.isTranscluded(path.node)) {
|
|
131
|
+
path.skip();
|
|
132
|
+
} else if (isJSONSchemaElementWith$id(path.node)) {
|
|
133
|
+
entries.push({
|
|
134
|
+
schema: path.node,
|
|
135
|
+
$ids: collectSchema$ids(path)
|
|
136
|
+
});
|
|
211
137
|
}
|
|
212
138
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
trace: []
|
|
226
|
-
});
|
|
139
|
+
});
|
|
140
|
+
this.schema$idIndex.set(element, entries);
|
|
141
|
+
}
|
|
142
|
+
enter(path) {
|
|
143
|
+
if (this.isTranscluded(path.node)) {
|
|
144
|
+
path.skip();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// a Source Description parsed into the document has its own components: it's dereferenced
|
|
148
|
+
// in its own context by the strategy, never by the document referencing it
|
|
149
|
+
if (path.parentPath !== null && isParseResultElement(path.node) && includesClasses(path.node, ['source-description'])) {
|
|
150
|
+
path.skip();
|
|
227
151
|
}
|
|
228
|
-
if (!isEntryDocument || continueOnError === false) throw unresolvedError;
|
|
229
|
-
if (typeof continueOnError === 'function') continueOnError(unresolvedError);
|
|
230
152
|
}
|
|
231
|
-
ReusableElement(path) {
|
|
153
|
+
async ReusableElement(path) {
|
|
232
154
|
const referencingElement = path.node;
|
|
233
155
|
|
|
156
|
+
// the fields of a Reusable Object hold no references
|
|
157
|
+
path.skip();
|
|
158
|
+
|
|
234
159
|
// skip current Reusable Object if reference field is not defined as a string
|
|
235
160
|
if (!isStringElement(referencingElement.reference)) {
|
|
236
|
-
path.skip();
|
|
237
161
|
return;
|
|
238
162
|
}
|
|
239
163
|
|
|
240
164
|
// ignore if resolve.internal is false (Reusable Objects are internal references only)
|
|
241
165
|
if (!this.options.resolve.internal) {
|
|
242
|
-
path.skip();
|
|
243
166
|
return;
|
|
244
167
|
}
|
|
245
168
|
const runtimeExpression = toValue(referencingElement.reference);
|
|
246
|
-
let referencedElement;
|
|
247
169
|
try {
|
|
248
170
|
// parse the runtime expression
|
|
249
171
|
const {
|
|
@@ -265,6 +187,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
265
187
|
|
|
266
188
|
// evaluate runtime expression as JSON Pointer to get the referenced element
|
|
267
189
|
const jsonPointer = jsonPointerCompile(['components', tree.componentType, tree.componentName]);
|
|
190
|
+
let referencedElement;
|
|
268
191
|
try {
|
|
269
192
|
referencedElement = jsonPointerEvaluate(this.reference.value.result, jsonPointer);
|
|
270
193
|
} catch {
|
|
@@ -272,50 +195,62 @@ class Arazzo1DereferenceVisitor {
|
|
|
272
195
|
runtimeExpression
|
|
273
196
|
});
|
|
274
197
|
}
|
|
198
|
+
|
|
199
|
+
// the reference decides the type of the referenced component
|
|
200
|
+
const type = toValue(referencingElement.meta.get('referenced-element')) ?? referencedElement.element;
|
|
201
|
+
await this.addTransclusion(path, {
|
|
202
|
+
source: referencedElement,
|
|
203
|
+
type,
|
|
204
|
+
reference: this.reference,
|
|
205
|
+
context: {
|
|
206
|
+
ancestorSchema$ids: []
|
|
207
|
+
}
|
|
208
|
+
}, {
|
|
209
|
+
toTranscludedElement: (element, target) => {
|
|
210
|
+
const reusableElement = element;
|
|
211
|
+
const mergedElement = cloneShallow(target);
|
|
212
|
+
// annotate with info about original referencing element
|
|
213
|
+
mergedElement.meta.set('ref-fields', {
|
|
214
|
+
reference: runtimeExpression,
|
|
215
|
+
value: toValue(reusableElement.value)
|
|
216
|
+
});
|
|
217
|
+
// annotate with info about origin
|
|
218
|
+
mergedElement.meta.set('ref-origin', this.reference.uri);
|
|
219
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
220
|
+
|
|
221
|
+
// override value field if present for Parameter Objects
|
|
222
|
+
if (isParameterElement(mergedElement) && reusableElement.hasKey('value')) {
|
|
223
|
+
mergedElement.remove('value');
|
|
224
|
+
mergedElement.set('value', reusableElement.get('value'));
|
|
225
|
+
}
|
|
226
|
+
return mergedElement;
|
|
227
|
+
},
|
|
228
|
+
circular: {
|
|
229
|
+
uri: runtimeExpression,
|
|
230
|
+
meta: {
|
|
231
|
+
uri: this.reference.uri,
|
|
232
|
+
reference: runtimeExpression
|
|
233
|
+
},
|
|
234
|
+
details: {
|
|
235
|
+
runtimeExpression
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
275
239
|
} catch (error) {
|
|
276
240
|
this.handleError(`Error while dereferencing Reusable Object. Cannot resolve reference "${runtimeExpression}": ${error.message}`, error, referencingElement, 'reference', runtimeExpression, path);
|
|
277
|
-
return;
|
|
278
241
|
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Create a shallow clone of the referenced element to avoid modifying the original.
|
|
282
|
-
*/
|
|
283
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
284
|
-
// annotate with info about original referencing element
|
|
285
|
-
mergedElement.meta.set('ref-fields', {
|
|
286
|
-
reference: runtimeExpression,
|
|
287
|
-
value: toValue(referencingElement.value)
|
|
288
|
-
});
|
|
289
|
-
// annotate with info about origin
|
|
290
|
-
mergedElement.meta.set('ref-origin', this.reference.uri);
|
|
291
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
292
|
-
|
|
293
|
-
// override value field if present for Parameter Objects
|
|
294
|
-
if (isParameterElement(mergedElement) && referencingElement.hasKey('value')) {
|
|
295
|
-
mergedElement.remove('value');
|
|
296
|
-
mergedElement.set('value', referencingElement.get('value'));
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Transclude referencing element with merged referenced element.
|
|
301
|
-
*/
|
|
302
|
-
path.replaceWith(mergedElement);
|
|
303
|
-
path.skip();
|
|
304
242
|
}
|
|
305
243
|
async JSONSchemaElement(path) {
|
|
306
244
|
const referencingElement = path.node;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (!isStringElement(referencingElement.$ref)) {
|
|
245
|
+
if (this.isTranscluded(referencingElement)) {
|
|
246
|
+
path.skip();
|
|
310
247
|
return;
|
|
311
248
|
}
|
|
312
249
|
|
|
313
|
-
// skip current referencing
|
|
314
|
-
if (
|
|
315
|
-
path.skip();
|
|
250
|
+
// skip current referencing schema as $ref keyword was not defined
|
|
251
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
316
252
|
return;
|
|
317
253
|
}
|
|
318
|
-
const indirectionsSize = this.indirections.length;
|
|
319
254
|
try {
|
|
320
255
|
// compute baseURI using rules around $self, $id and $ref keywords
|
|
321
256
|
let reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
@@ -346,11 +281,12 @@ class Arazzo1DereferenceVisitor {
|
|
|
346
281
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
347
282
|
const selector = $refBaseURI;
|
|
348
283
|
const documentElement = reference.value.result;
|
|
349
|
-
const referenceAsSchema =
|
|
284
|
+
const referenceAsSchema = this.toJSONSchemaDocument(documentElement);
|
|
285
|
+
this.index$ids(referenceAsSchema);
|
|
350
286
|
// a document that is itself a fragment detached from its source carries
|
|
351
287
|
// the $ids enclosing it only as `ancestorSchema$ids`: its $ids are then
|
|
352
288
|
// indexed against the base that chain leaves in effect, and the chain
|
|
353
|
-
// prefixes the one of the located schema. A
|
|
289
|
+
// prefixes the one of the located schema. A visitor rooted at a
|
|
354
290
|
// fragment of a whole document indexes that document against its own base.
|
|
355
291
|
const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
|
|
356
292
|
const isDocumentRoot = (isParseResultElement(rootNode) ? rootNode.result : rootNode) === documentElement;
|
|
@@ -364,7 +300,6 @@ class Arazzo1DereferenceVisitor {
|
|
|
364
300
|
if (isDocumentRoot) {
|
|
365
301
|
ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
|
|
366
302
|
}
|
|
367
|
-
referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
|
|
368
303
|
|
|
369
304
|
// ignore resolving internal Schema Objects
|
|
370
305
|
if (!this.options.resolve.internal && isInternalReference) {
|
|
@@ -394,12 +329,11 @@ class Arazzo1DereferenceVisitor {
|
|
|
394
329
|
}
|
|
395
330
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
396
331
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
397
|
-
const referenceAsSchema =
|
|
332
|
+
const referenceAsSchema = this.toJSONSchemaDocument(reference.value.result);
|
|
398
333
|
({
|
|
399
334
|
element: referencedElement,
|
|
400
335
|
ancestorSchema$ids
|
|
401
336
|
} = jsonPointerLocate(referenceAsSchema, selector));
|
|
402
|
-
referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
|
|
403
337
|
}
|
|
404
338
|
} catch (error) {
|
|
405
339
|
/**
|
|
@@ -424,12 +358,11 @@ class Arazzo1DereferenceVisitor {
|
|
|
424
358
|
}
|
|
425
359
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
426
360
|
const selector = uriToAnchor($refBaseURI);
|
|
427
|
-
const referenceAsSchema =
|
|
361
|
+
const referenceAsSchema = this.toJSONSchemaDocument(reference.value.result);
|
|
428
362
|
({
|
|
429
363
|
element: referencedElement,
|
|
430
364
|
ancestorSchema$ids
|
|
431
365
|
} = $anchorLocate(selector, referenceAsSchema));
|
|
432
|
-
referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
|
|
433
366
|
} else {
|
|
434
367
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
435
368
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
@@ -448,140 +381,84 @@ class Arazzo1DereferenceVisitor {
|
|
|
448
381
|
}
|
|
449
382
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
450
383
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
451
|
-
const referenceAsSchema =
|
|
384
|
+
const referenceAsSchema = this.toJSONSchemaDocument(reference.value.result);
|
|
452
385
|
({
|
|
453
386
|
element: referencedElement,
|
|
454
387
|
ancestorSchema$ids
|
|
455
388
|
} = jsonPointerLocate(referenceAsSchema, selector));
|
|
456
|
-
referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
|
|
457
389
|
}
|
|
458
390
|
} else {
|
|
459
391
|
throw error;
|
|
460
392
|
}
|
|
461
393
|
}
|
|
462
|
-
this.indirections.push(referencingElement);
|
|
463
394
|
|
|
464
|
-
// detect direct
|
|
395
|
+
// detect direct reference
|
|
465
396
|
if (referencingElement === referencedElement) {
|
|
466
397
|
throw new ApiDOMStructuredError('Recursive JSON Schema reference detected', {
|
|
467
398
|
$ref: toValue(referencingElement.$ref)
|
|
468
399
|
});
|
|
469
400
|
}
|
|
470
401
|
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
402
|
+
// a JSON Schema $ref reads whatever it points at as a JSON Schema
|
|
403
|
+
const $ref = toValue(referencingElement.$ref);
|
|
404
|
+
const origin = reference.uri;
|
|
405
|
+
await this.addTransclusion(path, {
|
|
406
|
+
source: referencedElement,
|
|
407
|
+
type: JSON_SCHEMA_TYPE,
|
|
408
|
+
reference,
|
|
409
|
+
context: {
|
|
410
|
+
ancestorSchema$ids
|
|
411
|
+
}
|
|
412
|
+
}, {
|
|
413
|
+
toTranscludedElement: (element, target) => {
|
|
414
|
+
// Boolean JSON Schemas
|
|
415
|
+
if (isBooleanJSONSchemaElement(target)) {
|
|
416
|
+
const booleanJsonSchemaElement = cloneDeep(target);
|
|
417
|
+
// annotate referenced element with info about original referencing element
|
|
418
|
+
booleanJsonSchemaElement.meta.set('ref-fields', {
|
|
419
|
+
$ref
|
|
420
|
+
});
|
|
421
|
+
// annotate referenced element with info about origin
|
|
422
|
+
booleanJsonSchemaElement.meta.set('ref-origin', origin);
|
|
423
|
+
booleanJsonSchemaElement.meta.set('ref-type', element.element);
|
|
424
|
+
return booleanJsonSchemaElement;
|
|
425
|
+
}
|
|
426
|
+
if (!isJSONSchemaElement(target)) return target;
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Creating a new version of JSON Schema by merging fields from referenced Schema with referencing one.
|
|
430
|
+
*/
|
|
431
|
+
const mergedElement = cloneShallow(target);
|
|
432
|
+
// existing keywords from referencing schema overrides ones from referenced schema
|
|
433
|
+
element.forEach((value, keyElement, item) => {
|
|
434
|
+
mergedElement.remove(toValue(keyElement));
|
|
435
|
+
mergedElement.content.push(item);
|
|
486
436
|
});
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
$ref: toValue(referencingElement.$ref)
|
|
437
|
+
mergedElement.remove('$ref');
|
|
438
|
+
// annotate referenced element with info about original referencing element
|
|
439
|
+
mergedElement.meta.set('ref-fields', {
|
|
440
|
+
$ref
|
|
492
441
|
});
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
509
|
-
*/
|
|
510
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
511
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
512
|
-
// content transplanted into the referencing site is traversed under that
|
|
513
|
-
// site's $id chain; a fragment enclosed by a different chain has its own
|
|
514
|
-
// $refs resolved in place first, by a nested visitor seeded with that chain
|
|
515
|
-
const hasDifferentEnclosingBase = resolveSchema$ids(resolveArazzo$selfField(reference.uri, reference.value.result), ancestorSchema$ids) !== resolveSchema$ids(rootBaseURI, schema$idsOf(path.getAncestorNodes().reverse()));
|
|
516
|
-
if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || isJSONSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
517
|
-
if (shouldDetectCircular) {
|
|
518
|
-
this.visited.add(referencedElement);
|
|
442
|
+
// annotate fragment with info about origin
|
|
443
|
+
mergedElement.meta.set('ref-origin', origin);
|
|
444
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
445
|
+
return mergedElement;
|
|
446
|
+
},
|
|
447
|
+
aliasField: '$ref',
|
|
448
|
+
circular: {
|
|
449
|
+
uri: $refBaseURI,
|
|
450
|
+
meta: {
|
|
451
|
+
uri: origin,
|
|
452
|
+
$ref
|
|
453
|
+
},
|
|
454
|
+
details: {
|
|
455
|
+
$ref
|
|
456
|
+
}
|
|
519
457
|
}
|
|
520
|
-
|
|
521
|
-
// append referencing reference to ancestors lineage
|
|
522
|
-
directAncestors.add(referencingElement);
|
|
523
|
-
const visitor = new Arazzo1DereferenceVisitor({
|
|
524
|
-
reference,
|
|
525
|
-
indirections: [...this.indirections],
|
|
526
|
-
options: this.options,
|
|
527
|
-
schema$idIndex: this.schema$idIndex,
|
|
528
|
-
visited: this.visited,
|
|
529
|
-
ancestors: ancestorsLineage,
|
|
530
|
-
ancestorSchema$ids
|
|
531
|
-
});
|
|
532
|
-
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
533
|
-
mutable: true,
|
|
534
|
-
skipVisited: 'skip'
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
// remove referencing reference from ancestors lineage
|
|
538
|
-
directAncestors.delete(referencingElement);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Boolean JSON Schemas
|
|
542
|
-
if (isBooleanJSONSchemaElement(referencedElement)) {
|
|
543
|
-
const booleanJsonSchemaElement = cloneDeep(referencedElement);
|
|
544
|
-
// annotate referenced element with info about original referencing element
|
|
545
|
-
booleanJsonSchemaElement.meta.set('ref-fields', {
|
|
546
|
-
$ref: toValue(referencingElement.$ref)
|
|
547
|
-
});
|
|
548
|
-
// annotate referenced element with info about origin
|
|
549
|
-
booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
|
|
550
|
-
booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
|
|
551
|
-
path.replaceWith(booleanJsonSchemaElement);
|
|
552
|
-
path.skip();
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Creating a new version of JSON Schema by merging fields from referenced Schema with referencing one.
|
|
558
|
-
*/
|
|
559
|
-
if (isJSONSchemaElement(referencedElement)) {
|
|
560
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
561
|
-
// existing keywords from referencing schema overrides ones from referenced schema
|
|
562
|
-
referencingElement.forEach((value, keyElement, item) => {
|
|
563
|
-
mergedElement.remove(toValue(keyElement));
|
|
564
|
-
mergedElement.content.push(item);
|
|
565
|
-
});
|
|
566
|
-
mergedElement.remove('$ref');
|
|
567
|
-
// annotate referenced element with info about original referencing element
|
|
568
|
-
mergedElement.meta.set('ref-fields', {
|
|
569
|
-
$ref: toValue(referencingElement.$ref)
|
|
570
|
-
});
|
|
571
|
-
// annotate fragment with info about origin
|
|
572
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
573
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
574
|
-
referencedElement = mergedElement;
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Transclude referencing element with merged referenced element.
|
|
578
|
-
*/
|
|
579
|
-
path.replaceWith(referencedElement);
|
|
458
|
+
});
|
|
580
459
|
} catch (error) {
|
|
581
460
|
const $ref = toValue(referencingElement.$ref);
|
|
582
461
|
this.handleError(`Error while dereferencing Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
583
|
-
} finally {
|
|
584
|
-
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
585
462
|
}
|
|
586
463
|
}
|
|
587
464
|
}
|