@speclynx/apidom-reference 4.13.0 → 4.15.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 +12 -0
- package/README.md +99 -3
- package/dist/apidom-reference.browser.js +2076 -216
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +33 -28
- package/src/bundle/strategies/arazzo-1/index.cjs +94 -0
- package/src/bundle/strategies/arazzo-1/index.mjs +81 -0
- package/src/bundle/strategies/arazzo-1/visitor.cjs +417 -0
- package/src/bundle/strategies/arazzo-1/visitor.mjs +409 -0
- package/src/bundle/strategies/openapi-2/visitor.cjs +8 -16
- package/src/bundle/strategies/openapi-2/visitor.mjs +8 -16
- package/src/bundle/strategies/openapi-3-0/visitor.cjs +10 -18
- package/src/bundle/strategies/openapi-3-0/visitor.mjs +10 -18
- package/src/bundle/strategies/openapi-3-1/index.cjs +59 -2
- package/src/bundle/strategies/openapi-3-1/index.mjs +54 -2
- package/src/bundle/strategies/openapi-3-1/visitor.cjs +811 -0
- package/src/bundle/strategies/openapi-3-1/visitor.mjs +803 -0
- package/src/configuration/saturated.cjs +10 -9
- package/src/configuration/saturated.mjs +2 -1
- package/src/util/url.cjs +26 -4
- package/src/util/url.mjs +23 -2
- package/types/apidom-reference.d.ts +10 -0
- package/types/bundle/strategies/arazzo-1/index.d.ts +31 -0
- package/types/bundle/strategies/arazzo-1/visitor.d.ts +90 -0
- package/types/bundle/strategies/openapi-2/visitor.d.ts +2 -2
- package/types/bundle/strategies/openapi-3-0/visitor.d.ts +2 -2
- package/types/bundle/strategies/openapi-3-1/index.d.ts +6 -1
- package/types/bundle/strategies/openapi-3-1/visitor.d.ts +141 -0
- package/types/util/url.d.ts +8 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { none } from 'ramda';
|
|
2
|
+
import { isElement, isObjectElement, isStringElement, AnnotationElement, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
3
|
+
import { toValue, toYAML, fixedFields } from '@speclynx/apidom-core';
|
|
4
|
+
import { traverseAsync } from '@speclynx/apidom-traverse';
|
|
5
|
+
import { evaluate as jsonPointerEvaluate, escape, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
|
|
6
|
+
import { ComponentsElement, ComponentsInputsElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
7
|
+
import BundleError from "../../../errors/BundleError.mjs";
|
|
8
|
+
import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
|
|
9
|
+
import MaximumBundleDepthError from "../../../errors/MaximumBundleDepthError.mjs";
|
|
10
|
+
import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
|
|
11
|
+
import EvaluationJsonSchemaUriError from "../../../errors/EvaluationJsonSchemaUriError.mjs";
|
|
12
|
+
import * as url from "../../../util/url.mjs";
|
|
13
|
+
import parse from "../../../parse/index.mjs";
|
|
14
|
+
import Reference from "../../../Reference.mjs";
|
|
15
|
+
import File from "../../../File.mjs";
|
|
16
|
+
import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "../../../dereference/strategies/arazzo-1/selectors/$anchor.mjs";
|
|
17
|
+
import { evaluate as uriEvaluate } from "../../../dereference/strategies/arazzo-1/selectors/uri.mjs";
|
|
18
|
+
import { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement } from "../../../dereference/strategies/arazzo-1/util.mjs";
|
|
19
|
+
import { toPascalCase, sanitizeComponentName, uniqueName as resolveUniqueName } from "../../util.mjs";
|
|
20
|
+
/**
|
|
21
|
+
* The Components Object field where embedded JSON Schema resources are hoisted.
|
|
22
|
+
* Sourced from the Components Object fixed fields so it cannot drift from the
|
|
23
|
+
* namespace definition.
|
|
24
|
+
*/
|
|
25
|
+
const cf = fixedFields(ComponentsElement, {
|
|
26
|
+
indexed: true
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
class Arazzo1BundleVisitor {
|
|
37
|
+
reference;
|
|
38
|
+
options;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The entry parse result. Derived from the shared refSet's root reference.
|
|
42
|
+
*/
|
|
43
|
+
get entryParseResult() {
|
|
44
|
+
return this.reference.refSet.rootRef.value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The entry document element. Embedded external schema resources are placed
|
|
49
|
+
* into its `components.inputs` object. The `canBundle` guard guarantees it is
|
|
50
|
+
* an ArazzoSpecification1Element.
|
|
51
|
+
*/
|
|
52
|
+
get entryResult() {
|
|
53
|
+
return this.entryParseResult.result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Shared across the entry document and every external document visitor.
|
|
58
|
+
* Guarantees each external resource is embedded exactly once, keyed by its
|
|
59
|
+
* resource URI.
|
|
60
|
+
*/
|
|
61
|
+
assignments;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Component names already reserved per Components Object field. Reserved
|
|
65
|
+
* before recursion so collision suffixing (`Pet`, `Pet-2`, ...) accounts for
|
|
66
|
+
* components that are still being bundled.
|
|
67
|
+
*/
|
|
68
|
+
reservedNames;
|
|
69
|
+
constructor({
|
|
70
|
+
reference,
|
|
71
|
+
options,
|
|
72
|
+
assignments = new Map(),
|
|
73
|
+
reservedNames = new Map()
|
|
74
|
+
}) {
|
|
75
|
+
this.reference = reference;
|
|
76
|
+
this.options = options;
|
|
77
|
+
this.assignments = assignments;
|
|
78
|
+
this.reservedNames = reservedNames;
|
|
79
|
+
}
|
|
80
|
+
toBaseURI(uri) {
|
|
81
|
+
return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
|
|
82
|
+
}
|
|
83
|
+
async toReference(uri) {
|
|
84
|
+
// detect maximum depth of resolution
|
|
85
|
+
if (this.reference.depth >= this.options.resolve.maxDepth) {
|
|
86
|
+
throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
|
|
87
|
+
maxDepth: this.options.resolve.maxDepth,
|
|
88
|
+
uri: this.reference.uri
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const baseURI = this.toBaseURI(uri);
|
|
92
|
+
const {
|
|
93
|
+
refSet
|
|
94
|
+
} = this.reference;
|
|
95
|
+
|
|
96
|
+
// we've already processed this Reference in past
|
|
97
|
+
if (refSet.has(baseURI)) {
|
|
98
|
+
return refSet.find(ref => ref.uri === baseURI);
|
|
99
|
+
}
|
|
100
|
+
const parseResult = await parse(url.unsanitize(baseURI), {
|
|
101
|
+
...this.options,
|
|
102
|
+
parse: {
|
|
103
|
+
...this.options.parse,
|
|
104
|
+
mediaType: 'text/plain'
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const reference = new Reference({
|
|
108
|
+
uri: baseURI,
|
|
109
|
+
value: parseResult,
|
|
110
|
+
depth: this.reference.depth + 1
|
|
111
|
+
});
|
|
112
|
+
refSet.add(reference);
|
|
113
|
+
return reference;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Handles an error according to the `bundle.continueOnError` option.
|
|
118
|
+
*
|
|
119
|
+
* For new errors: wraps in UnresolvableReferenceError with structured
|
|
120
|
+
* context. For errors already wrapped by a nested visitor: prepends the
|
|
121
|
+
* current hop to the trace.
|
|
122
|
+
*
|
|
123
|
+
* Inner/intermediate visitors always throw to let the trace accumulate. Only
|
|
124
|
+
* the entry document visitor respects continueOnError (callback/swallow/throw).
|
|
125
|
+
*/
|
|
126
|
+
handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
|
|
127
|
+
// a collision reported as `error` is a deliberate hard stop with no
|
|
128
|
+
// resolution-failure analog, so pass it through unchanged. A maxDepth
|
|
129
|
+
// breach (MaximumBundleDepthError, which also extends BundleError) is a
|
|
130
|
+
// resolution failure and flows through below — wrapped and subject to
|
|
131
|
+
// continueOnError, like dereferencing.
|
|
132
|
+
if (error instanceof BundleError && !(error instanceof MaximumBundleDepthError)) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
const {
|
|
136
|
+
continueOnError
|
|
137
|
+
} = this.options.bundle;
|
|
138
|
+
const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
|
|
139
|
+
const uri = this.reference.uri;
|
|
140
|
+
const type = referencingElement.element;
|
|
141
|
+
const codeFrame = toYAML(referencingElement);
|
|
142
|
+
const location = visitorPath.formatPath();
|
|
143
|
+
const hop = {
|
|
144
|
+
uri,
|
|
145
|
+
type,
|
|
146
|
+
refFieldName,
|
|
147
|
+
refFieldValue,
|
|
148
|
+
location,
|
|
149
|
+
codeFrame
|
|
150
|
+
};
|
|
151
|
+
let unresolvedError;
|
|
152
|
+
if (error instanceof UnresolvableReferenceError) {
|
|
153
|
+
// @ts-ignore
|
|
154
|
+
error.trace = [hop, ...error.trace];
|
|
155
|
+
unresolvedError = error;
|
|
156
|
+
} else {
|
|
157
|
+
unresolvedError = new UnresolvableReferenceError(message, {
|
|
158
|
+
cause: error,
|
|
159
|
+
type,
|
|
160
|
+
uri,
|
|
161
|
+
location,
|
|
162
|
+
codeFrame,
|
|
163
|
+
refFieldName,
|
|
164
|
+
refFieldValue,
|
|
165
|
+
trace: []
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (!isEntryDocument || continueOnError === false) throw unresolvedError;
|
|
169
|
+
if (typeof continueOnError === 'function') continueOnError(unresolvedError);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Lazily creates the Components Object (and the `inputs` field within it) on
|
|
174
|
+
* the entry document, returning the field where a resource is embedded.
|
|
175
|
+
*/
|
|
176
|
+
ensureInputsField() {
|
|
177
|
+
const entryResult = this.entryResult;
|
|
178
|
+
let components = entryResult.components;
|
|
179
|
+
if (!isObjectElement(components)) {
|
|
180
|
+
components = new ComponentsElement();
|
|
181
|
+
entryResult.components = components;
|
|
182
|
+
}
|
|
183
|
+
let inputs = components.get(cf.inputs.name);
|
|
184
|
+
if (!isObjectElement(inputs)) {
|
|
185
|
+
inputs = new ComponentsInputsElement();
|
|
186
|
+
components.set(cf.inputs.name, inputs);
|
|
187
|
+
}
|
|
188
|
+
return inputs;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Computes the base component name (before collision suffixing) according to
|
|
193
|
+
* the configured naming strategy.
|
|
194
|
+
*/
|
|
195
|
+
baseName(element, baseURI) {
|
|
196
|
+
// strategy specific options take precedence over the top-level bundle options
|
|
197
|
+
const componentNamesStrategy = this.options.bundle.strategyOpts['arazzo-1']?.componentNamesStrategy ?? this.options.bundle.componentNamesStrategy;
|
|
198
|
+
const fallback = url.getBasename(baseURI) || 'Schema';
|
|
199
|
+
if (typeof componentNamesStrategy === 'function') {
|
|
200
|
+
const resolved = componentNamesStrategy({
|
|
201
|
+
element,
|
|
202
|
+
field: cf.inputs.name,
|
|
203
|
+
jsonPointer: '',
|
|
204
|
+
baseURI
|
|
205
|
+
});
|
|
206
|
+
if (typeof resolved === 'string' && resolved !== '') {
|
|
207
|
+
return resolved;
|
|
208
|
+
}
|
|
209
|
+
return fallback;
|
|
210
|
+
}
|
|
211
|
+
if (componentNamesStrategy === 'title') {
|
|
212
|
+
const title = isObjectElement(element) ? toValue(element.get('title')) : undefined;
|
|
213
|
+
if (typeof title === 'string' && title.trim() !== '') {
|
|
214
|
+
const name = sanitizeComponentName(toPascalCase(title));
|
|
215
|
+
if (name !== '') return name;
|
|
216
|
+
}
|
|
217
|
+
// fall back to basename when no usable title is present
|
|
218
|
+
}
|
|
219
|
+
return fallback;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Computes a collision-free component name within `components.inputs`. A name
|
|
224
|
+
* is taken if it's already placed in inputs or reserved by a component that is
|
|
225
|
+
* still being bundled (reserved before recursion).
|
|
226
|
+
*/
|
|
227
|
+
uniqueName(candidate) {
|
|
228
|
+
const field = cf.inputs.name;
|
|
229
|
+
const inputs = this.ensureInputsField();
|
|
230
|
+
const reserved = this.reservedNames.get(field) ?? new Set();
|
|
231
|
+
return resolveUniqueName(candidate, name => inputs.hasKey(name) || reserved.has(name));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Reports a collision-forced rename according to the configured severity.
|
|
236
|
+
* A rename means two distinct targets resolved to the same name; each keeps
|
|
237
|
+
* its own component (and origin), so report the rename per severity.
|
|
238
|
+
*/
|
|
239
|
+
reportNameCollision(preferredName, componentName) {
|
|
240
|
+
// strategy specific options take precedence over the top-level bundle options
|
|
241
|
+
const onComponentNameCollision = this.options.bundle.strategyOpts['arazzo-1']?.onComponentNameCollision ?? this.options.bundle.onComponentNameCollision;
|
|
242
|
+
if (componentName === preferredName || onComponentNameCollision === 'off') {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const message = `Component "${preferredName}" in components/${cf.inputs.name} is referenced with the same name but different content. Renamed to "${componentName}".`;
|
|
246
|
+
if (onComponentNameCollision === 'error') {
|
|
247
|
+
throw new BundleError(message);
|
|
248
|
+
}
|
|
249
|
+
const annotation = new AnnotationElement(message);
|
|
250
|
+
annotation.classes.push('warning');
|
|
251
|
+
annotation.code = 'bundle-component-name-collision';
|
|
252
|
+
// append (never prepend) so the result element stays ahead of annotations
|
|
253
|
+
this.entryParseResult.content.push(annotation);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* JSON Schema Objects in Arazzo (used by Input Objects and inline schemas) are
|
|
258
|
+
* a JSON Schema 2020-12 dialect — the only external reference type Arazzo
|
|
259
|
+
* defines. They are bundled per the JSON Schema Compound Document rules: the
|
|
260
|
+
* external schema RESOURCE is embedded verbatim into `components.inputs`
|
|
261
|
+
* (carrying its `$id`), and the referencing `$ref` is left UNCHANGED — it
|
|
262
|
+
* keeps resolving against the embedded resource's `$id`. This mirrors the
|
|
263
|
+
* openapi-3-1 strategy's Schema Object handling.
|
|
264
|
+
*/
|
|
265
|
+
async JSONSchemaElement(path) {
|
|
266
|
+
const referencingElement = path.node;
|
|
267
|
+
|
|
268
|
+
// skip current referencing schema as $ref keyword was not defined
|
|
269
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const $ref = toValue(referencingElement.$ref);
|
|
273
|
+
try {
|
|
274
|
+
// compute baseURI using rules around $id and $ref keywords. The current
|
|
275
|
+
// document's URI is the retrieval URI — derive it directly (not via
|
|
276
|
+
// toReference) so an internal $ref needs no resolution and never trips the
|
|
277
|
+
// resolve.maxDepth guard.
|
|
278
|
+
const retrievalURI = this.reference.uri;
|
|
279
|
+
const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement);
|
|
280
|
+
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
281
|
+
const file = new File({
|
|
282
|
+
uri: $refBaseURIStrippedHash
|
|
283
|
+
});
|
|
284
|
+
const isUnknownURI = none(r => r.canRead(file), this.options.resolve.resolvers);
|
|
285
|
+
const isURL = !isUnknownURI;
|
|
286
|
+
const isInternalReference = url.stripHash(this.reference.uri) === $refBaseURIStrippedHash;
|
|
287
|
+
const isExternalReference = !isInternalReference;
|
|
288
|
+
|
|
289
|
+
// internal Schema Object $refs resolve against the in-document $id graph;
|
|
290
|
+
// leaving them untouched keeps that resolution valid. A JSON Schema $ref is
|
|
291
|
+
// never normalized to a bare fragment.
|
|
292
|
+
if (isInternalReference) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// honor the resolve.external switch
|
|
297
|
+
if (!this.options.resolve.external && isExternalReference) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// detect maximum depth of bundling before fetching the external resource
|
|
302
|
+
if (this.reference.depth >= this.options.bundle.maxDepth) {
|
|
303
|
+
throw new MaximumBundleDepthError(`Maximum bundle depth of "${this.options.bundle.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
304
|
+
maxDepth: this.options.bundle.maxDepth,
|
|
305
|
+
uri: this.reference.uri
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// locate the external schema resource the $ref targets, reusing the
|
|
310
|
+
// dereference strategy's classification. First try to resolve the target
|
|
311
|
+
// as a canonical URI / URL against the current document's $id graph; if
|
|
312
|
+
// that fails, fall back to fetching the external document and resolving
|
|
313
|
+
// the fragment there as a $anchor or JSON Pointer.
|
|
314
|
+
let schemaReference = this.reference;
|
|
315
|
+
try {
|
|
316
|
+
const referenceAsSchema = maybeRefractToJSONSchemaElement(schemaReference.value.result);
|
|
317
|
+
uriEvaluate($refBaseURI, referenceAsSchema);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (isURL && error instanceof EvaluationJsonSchemaUriError) {
|
|
320
|
+
if (isAnchor(uriToAnchor($refBaseURI))) {
|
|
321
|
+
schemaReference = await this.toReference(url.unsanitize($refBaseURI));
|
|
322
|
+
const selector = uriToAnchor($refBaseURI);
|
|
323
|
+
const referenceAsSchema = maybeRefractToJSONSchemaElement(schemaReference.value.result);
|
|
324
|
+
$anchorEvaluate(selector, referenceAsSchema);
|
|
325
|
+
} else {
|
|
326
|
+
schemaReference = await this.toReference(url.unsanitize($refBaseURI));
|
|
327
|
+
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
328
|
+
const referenceAsSchema = maybeRefractToJSONSchemaElement(schemaReference.value.result);
|
|
329
|
+
jsonPointerEvaluate(referenceAsSchema, selector);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// a $ref that resolves (by $id/$anchor/pointer) within the current document
|
|
337
|
+
// is internal even when its URI form (e.g. a URN or absolute $id) is not a
|
|
338
|
+
// bare fragment. Leave it untouched — it resolves against the in-document
|
|
339
|
+
// $id graph, and embedding the current document into itself would be wrong.
|
|
340
|
+
if (url.stripHash(schemaReference.uri) === url.stripHash(this.reference.uri)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// the resource to embed is the WHOLE external document (its root schema),
|
|
345
|
+
// identified by its $id. A $ref into a fragment of the document embeds the
|
|
346
|
+
// entire document resource once; the fragment keeps resolving against the
|
|
347
|
+
// embedded $id.
|
|
348
|
+
const resourceRoot = maybeRefractToJSONSchemaElement(schemaReference.value.result);
|
|
349
|
+
const resourceBaseURI = resolveSchema$idField(url.stripHash(schemaReference.uri), resourceRoot) ?? url.stripHash(schemaReference.uri);
|
|
350
|
+
const field = cf.inputs.name;
|
|
351
|
+
const canonicalKey = `${field}\t${resourceBaseURI}`;
|
|
352
|
+
|
|
353
|
+
// resource already embedded (or being embedded) — leave the referencing
|
|
354
|
+
// $ref untouched and stop. This also terminates circular external schema
|
|
355
|
+
// references, since the assignment is reserved BEFORE recursion.
|
|
356
|
+
if (this.assignments.has(canonicalKey)) {
|
|
357
|
+
path.skip();
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// own a copy of the resource and ensure it carries a $id so the unchanged
|
|
362
|
+
// $ref still resolves against it once embedded
|
|
363
|
+
const embeddedElement = cloneDeep(resourceRoot);
|
|
364
|
+
if (!isStringElement(embeddedElement.$id)) {
|
|
365
|
+
embeddedElement.set('$id', resourceBaseURI);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// the embedded thing is the WHOLE resource (keyed by its $id), so its name
|
|
369
|
+
// is derived from the resource URI
|
|
370
|
+
const preferredName = this.baseName(embeddedElement, resourceBaseURI);
|
|
371
|
+
const componentName = this.uniqueName(preferredName);
|
|
372
|
+
this.reportNameCollision(preferredName, componentName);
|
|
373
|
+
|
|
374
|
+
// reserve the assignment and component name before recursing so circular
|
|
375
|
+
// external schema references terminate
|
|
376
|
+
this.assignments.set(canonicalKey, `#/components/${field}/${escape(componentName)}`);
|
|
377
|
+
if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
|
|
378
|
+
this.reservedNames.get(field).add(componentName);
|
|
379
|
+
|
|
380
|
+
// bundle external references contained within the embedded resource;
|
|
381
|
+
// nested external resources land flat in the same components.inputs
|
|
382
|
+
const visitor = new Arazzo1BundleVisitor({
|
|
383
|
+
reference: schemaReference,
|
|
384
|
+
options: this.options,
|
|
385
|
+
assignments: this.assignments,
|
|
386
|
+
reservedNames: this.reservedNames
|
|
387
|
+
});
|
|
388
|
+
const bundledElement = await traverseAsync(embeddedElement, visitor, {
|
|
389
|
+
mutable: true
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// annotate the embedded resource with info about its origin
|
|
393
|
+
if (isElement(bundledElement)) {
|
|
394
|
+
bundledElement.meta.set('ref-origin', schemaReference.uri);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// place the embedded resource into the entry document's components.inputs
|
|
398
|
+
this.ensureInputsField().set(componentName, bundledElement);
|
|
399
|
+
|
|
400
|
+
// the referencing $ref is left UNCHANGED — it resolves against the
|
|
401
|
+
// embedded resource's $id
|
|
402
|
+
path.skip();
|
|
403
|
+
} catch (error) {
|
|
404
|
+
this.handleError(`Error while bundling JSON Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
405
|
+
path.skip();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
export default Arazzo1BundleVisitor;
|
|
@@ -177,11 +177,11 @@ class OpenAPI2BundleVisitor {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
/**
|
|
180
|
-
* Normalizes a self-
|
|
180
|
+
* Normalizes a self-reference (e.g. `./root.json#/definitions/Pet`)
|
|
181
181
|
* to a bare fragment (`#/definitions/Pet`) so the bundled document stays
|
|
182
182
|
* transferable. Bare fragments are returned unchanged.
|
|
183
183
|
*/
|
|
184
|
-
|
|
184
|
+
normalizeSelfReference(ref) {
|
|
185
185
|
return ref.startsWith('#') ? ref : url.getHash(ref);
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -262,16 +262,8 @@ class OpenAPI2BundleVisitor {
|
|
|
262
262
|
*/
|
|
263
263
|
basenameOf(jsonPointer, baseURI) {
|
|
264
264
|
const tokens = (0, _apidomJsonPointer.parse)(jsonPointer).tree;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const lastSlash = baseURI.lastIndexOf('/');
|
|
268
|
-
const lastDot = baseURI.lastIndexOf('.');
|
|
269
|
-
const fileName = baseURI.slice(lastSlash + 1);
|
|
270
|
-
// strip the extension only when the last dot belongs to the file name
|
|
271
|
-
candidate = lastDot > lastSlash ? baseURI.slice(lastSlash + 1, lastDot) : fileName;
|
|
272
|
-
}
|
|
273
|
-
if (candidate === '') candidate = 'Schema';
|
|
274
|
-
return candidate;
|
|
265
|
+
const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] : '';
|
|
266
|
+
return lastToken || url.getBasename(baseURI) || 'Schema';
|
|
275
267
|
}
|
|
276
268
|
|
|
277
269
|
/**
|
|
@@ -326,9 +318,9 @@ class OpenAPI2BundleVisitor {
|
|
|
326
318
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
327
319
|
const isExternalReference = !isInternalReference;
|
|
328
320
|
if (isInternalReference) {
|
|
329
|
-
// normalize self-
|
|
321
|
+
// normalize self-references to a bare fragment so the bundled
|
|
330
322
|
// document stays transferable; bare fragments are left untouched
|
|
331
|
-
referencingElement.set('$ref', this.
|
|
323
|
+
referencingElement.set('$ref', this.normalizeSelfReference($ref));
|
|
332
324
|
path.skip();
|
|
333
325
|
return;
|
|
334
326
|
}
|
|
@@ -485,9 +477,9 @@ class OpenAPI2BundleVisitor {
|
|
|
485
477
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
486
478
|
const isExternalReference = !isInternalReference;
|
|
487
479
|
if (isInternalReference) {
|
|
488
|
-
// normalize self-
|
|
480
|
+
// normalize self-references to a bare fragment so the bundled
|
|
489
481
|
// document stays transferable; bare fragments are left untouched
|
|
490
|
-
pathItemElement.set('$ref', this.
|
|
482
|
+
pathItemElement.set('$ref', this.normalizeSelfReference($ref));
|
|
491
483
|
return;
|
|
492
484
|
}
|
|
493
485
|
|
|
@@ -169,11 +169,11 @@ class OpenAPI2BundleVisitor {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
|
-
* Normalizes a self-
|
|
172
|
+
* Normalizes a self-reference (e.g. `./root.json#/definitions/Pet`)
|
|
173
173
|
* to a bare fragment (`#/definitions/Pet`) so the bundled document stays
|
|
174
174
|
* transferable. Bare fragments are returned unchanged.
|
|
175
175
|
*/
|
|
176
|
-
|
|
176
|
+
normalizeSelfReference(ref) {
|
|
177
177
|
return ref.startsWith('#') ? ref : url.getHash(ref);
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -254,16 +254,8 @@ class OpenAPI2BundleVisitor {
|
|
|
254
254
|
*/
|
|
255
255
|
basenameOf(jsonPointer, baseURI) {
|
|
256
256
|
const tokens = parseJSONPointer(jsonPointer).tree;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const lastSlash = baseURI.lastIndexOf('/');
|
|
260
|
-
const lastDot = baseURI.lastIndexOf('.');
|
|
261
|
-
const fileName = baseURI.slice(lastSlash + 1);
|
|
262
|
-
// strip the extension only when the last dot belongs to the file name
|
|
263
|
-
candidate = lastDot > lastSlash ? baseURI.slice(lastSlash + 1, lastDot) : fileName;
|
|
264
|
-
}
|
|
265
|
-
if (candidate === '') candidate = 'Schema';
|
|
266
|
-
return candidate;
|
|
257
|
+
const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] : '';
|
|
258
|
+
return lastToken || url.getBasename(baseURI) || 'Schema';
|
|
267
259
|
}
|
|
268
260
|
|
|
269
261
|
/**
|
|
@@ -318,9 +310,9 @@ class OpenAPI2BundleVisitor {
|
|
|
318
310
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
319
311
|
const isExternalReference = !isInternalReference;
|
|
320
312
|
if (isInternalReference) {
|
|
321
|
-
// normalize self-
|
|
313
|
+
// normalize self-references to a bare fragment so the bundled
|
|
322
314
|
// document stays transferable; bare fragments are left untouched
|
|
323
|
-
referencingElement.set('$ref', this.
|
|
315
|
+
referencingElement.set('$ref', this.normalizeSelfReference($ref));
|
|
324
316
|
path.skip();
|
|
325
317
|
return;
|
|
326
318
|
}
|
|
@@ -477,9 +469,9 @@ class OpenAPI2BundleVisitor {
|
|
|
477
469
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
478
470
|
const isExternalReference = !isInternalReference;
|
|
479
471
|
if (isInternalReference) {
|
|
480
|
-
// normalize self-
|
|
472
|
+
// normalize self-references to a bare fragment so the bundled
|
|
481
473
|
// document stays transferable; bare fragments are left untouched
|
|
482
|
-
pathItemElement.set('$ref', this.
|
|
474
|
+
pathItemElement.set('$ref', this.normalizeSelfReference($ref));
|
|
483
475
|
return;
|
|
484
476
|
}
|
|
485
477
|
|
|
@@ -188,11 +188,11 @@ class OpenAPI3_0BundleVisitor {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
|
-
* Normalizes a self-
|
|
191
|
+
* Normalizes a self-reference (e.g. `./root.json#/components/schemas/Pet`)
|
|
192
192
|
* to a bare fragment (`#/components/schemas/Pet`) so the bundled document stays
|
|
193
193
|
* transferable. Bare fragments are returned unchanged.
|
|
194
194
|
*/
|
|
195
|
-
|
|
195
|
+
normalizeSelfReference(ref) {
|
|
196
196
|
return ref.startsWith('#') ? ref : url.getHash(ref);
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -278,16 +278,8 @@ class OpenAPI3_0BundleVisitor {
|
|
|
278
278
|
*/
|
|
279
279
|
basenameOf(jsonPointer, baseURI) {
|
|
280
280
|
const tokens = (0, _apidomJsonPointer.parse)(jsonPointer).tree;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const lastSlash = baseURI.lastIndexOf('/');
|
|
284
|
-
const lastDot = baseURI.lastIndexOf('.');
|
|
285
|
-
const fileName = baseURI.slice(lastSlash + 1);
|
|
286
|
-
// strip the extension only when the last dot belongs to the file name
|
|
287
|
-
candidate = lastDot > lastSlash ? baseURI.slice(lastSlash + 1, lastDot) : fileName;
|
|
288
|
-
}
|
|
289
|
-
if (candidate === '') candidate = 'Schema';
|
|
290
|
-
return candidate;
|
|
281
|
+
const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] : '';
|
|
282
|
+
return lastToken || url.getBasename(baseURI) || 'Schema';
|
|
291
283
|
}
|
|
292
284
|
|
|
293
285
|
/**
|
|
@@ -337,9 +329,9 @@ class OpenAPI3_0BundleVisitor {
|
|
|
337
329
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
338
330
|
const isExternalReference = !isInternalReference;
|
|
339
331
|
if (isInternalReference) {
|
|
340
|
-
// normalize self-
|
|
332
|
+
// normalize self-references to a bare fragment so the bundled
|
|
341
333
|
// document stays transferable; bare fragments are left untouched
|
|
342
|
-
referencingElement.set('$ref', this.
|
|
334
|
+
referencingElement.set('$ref', this.normalizeSelfReference($ref));
|
|
343
335
|
path.skip();
|
|
344
336
|
return;
|
|
345
337
|
}
|
|
@@ -481,9 +473,9 @@ class OpenAPI3_0BundleVisitor {
|
|
|
481
473
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
482
474
|
const isExternalReference = !isInternalReference;
|
|
483
475
|
if (isInternalReference) {
|
|
484
|
-
// normalize self-
|
|
476
|
+
// normalize self-references to a bare fragment so the bundled
|
|
485
477
|
// document stays transferable; bare fragments are left untouched
|
|
486
|
-
pathItemElement.set('$ref', this.
|
|
478
|
+
pathItemElement.set('$ref', this.normalizeSelfReference($ref));
|
|
487
479
|
return;
|
|
488
480
|
}
|
|
489
481
|
|
|
@@ -628,9 +620,9 @@ class OpenAPI3_0BundleVisitor {
|
|
|
628
620
|
const retrievalURI = this.toBaseURI(operationRef);
|
|
629
621
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
630
622
|
if (isInternalReference) {
|
|
631
|
-
// normalize self-
|
|
623
|
+
// normalize a self-reference operationRef to a bare fragment so the bundled
|
|
632
624
|
// document stays transferable; bare fragments are left untouched
|
|
633
|
-
linkElement.set('operationRef', this.
|
|
625
|
+
linkElement.set('operationRef', this.normalizeSelfReference(operationRef));
|
|
634
626
|
return;
|
|
635
627
|
}
|
|
636
628
|
|
|
@@ -180,11 +180,11 @@ class OpenAPI3_0BundleVisitor {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
|
-
* Normalizes a self-
|
|
183
|
+
* Normalizes a self-reference (e.g. `./root.json#/components/schemas/Pet`)
|
|
184
184
|
* to a bare fragment (`#/components/schemas/Pet`) so the bundled document stays
|
|
185
185
|
* transferable. Bare fragments are returned unchanged.
|
|
186
186
|
*/
|
|
187
|
-
|
|
187
|
+
normalizeSelfReference(ref) {
|
|
188
188
|
return ref.startsWith('#') ? ref : url.getHash(ref);
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -270,16 +270,8 @@ class OpenAPI3_0BundleVisitor {
|
|
|
270
270
|
*/
|
|
271
271
|
basenameOf(jsonPointer, baseURI) {
|
|
272
272
|
const tokens = parseJSONPointer(jsonPointer).tree;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const lastSlash = baseURI.lastIndexOf('/');
|
|
276
|
-
const lastDot = baseURI.lastIndexOf('.');
|
|
277
|
-
const fileName = baseURI.slice(lastSlash + 1);
|
|
278
|
-
// strip the extension only when the last dot belongs to the file name
|
|
279
|
-
candidate = lastDot > lastSlash ? baseURI.slice(lastSlash + 1, lastDot) : fileName;
|
|
280
|
-
}
|
|
281
|
-
if (candidate === '') candidate = 'Schema';
|
|
282
|
-
return candidate;
|
|
273
|
+
const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] : '';
|
|
274
|
+
return lastToken || url.getBasename(baseURI) || 'Schema';
|
|
283
275
|
}
|
|
284
276
|
|
|
285
277
|
/**
|
|
@@ -329,9 +321,9 @@ class OpenAPI3_0BundleVisitor {
|
|
|
329
321
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
330
322
|
const isExternalReference = !isInternalReference;
|
|
331
323
|
if (isInternalReference) {
|
|
332
|
-
// normalize self-
|
|
324
|
+
// normalize self-references to a bare fragment so the bundled
|
|
333
325
|
// document stays transferable; bare fragments are left untouched
|
|
334
|
-
referencingElement.set('$ref', this.
|
|
326
|
+
referencingElement.set('$ref', this.normalizeSelfReference($ref));
|
|
335
327
|
path.skip();
|
|
336
328
|
return;
|
|
337
329
|
}
|
|
@@ -473,9 +465,9 @@ class OpenAPI3_0BundleVisitor {
|
|
|
473
465
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
474
466
|
const isExternalReference = !isInternalReference;
|
|
475
467
|
if (isInternalReference) {
|
|
476
|
-
// normalize self-
|
|
468
|
+
// normalize self-references to a bare fragment so the bundled
|
|
477
469
|
// document stays transferable; bare fragments are left untouched
|
|
478
|
-
pathItemElement.set('$ref', this.
|
|
470
|
+
pathItemElement.set('$ref', this.normalizeSelfReference($ref));
|
|
479
471
|
return;
|
|
480
472
|
}
|
|
481
473
|
|
|
@@ -620,9 +612,9 @@ class OpenAPI3_0BundleVisitor {
|
|
|
620
612
|
const retrievalURI = this.toBaseURI(operationRef);
|
|
621
613
|
const isInternalReference = this.entryURI === retrievalURI;
|
|
622
614
|
if (isInternalReference) {
|
|
623
|
-
// normalize self-
|
|
615
|
+
// normalize a self-reference operationRef to a bare fragment so the bundled
|
|
624
616
|
// document stays transferable; bare fragments are left untouched
|
|
625
|
-
linkElement.set('operationRef', this.
|
|
617
|
+
linkElement.set('operationRef', this.normalizeSelfReference(operationRef));
|
|
626
618
|
return;
|
|
627
619
|
}
|
|
628
620
|
|