@speclynx/apidom-reference 5.2.4 → 5.2.5
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 +30 -11
- package/dist/apidom-reference.browser.js +1121 -514
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +28 -28
- package/src/bundle/strategies/arazzo-1/visitor.cjs +105 -22
- package/src/bundle/strategies/arazzo-1/visitor.mjs +107 -24
- package/src/bundle/strategies/openapi-3-1/visitor.cjs +197 -47
- package/src/bundle/strategies/openapi-3-1/visitor.mjs +200 -50
- package/src/bundle/util.cjs +57 -2
- package/src/bundle/util.mjs +52 -0
- package/src/dereference/strategies/arazzo-1/index.cjs +9 -7
- package/src/dereference/strategies/arazzo-1/index.mjs +3 -1
- package/src/dereference/strategies/arazzo-1/selectors/$anchor.cjs +6 -0
- package/src/dereference/strategies/arazzo-1/selectors/$anchor.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/selectors/uri.cjs +6 -0
- package/src/dereference/strategies/arazzo-1/selectors/uri.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/util.cjs +6 -0
- package/src/dereference/strategies/arazzo-1/util.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/visitor.cjs +67 -9
- package/src/dereference/strategies/arazzo-1/visitor.mjs +71 -13
- package/src/dereference/strategies/openapi-3-1/index.cjs +2 -1
- package/src/dereference/strategies/openapi-3-1/index.mjs +3 -1
- package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +16 -4
- package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +14 -3
- package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.cjs +29 -0
- package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.mjs +22 -0
- package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +54 -27
- package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +51 -27
- package/src/dereference/strategies/openapi-3-1/util.cjs +75 -14
- package/src/dereference/strategies/openapi-3-1/util.mjs +68 -14
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +85 -9
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +89 -13
- package/src/util/url.cjs +35 -2
- package/src/util/url.mjs +32 -0
- package/types/apidom-reference.d.ts +12 -0
- package/types/bundle/strategies/arazzo-1/index.d.ts +1 -1
- package/types/bundle/strategies/arazzo-1/visitor.d.ts +51 -6
- package/types/bundle/strategies/openapi-3-1/index.d.ts +1 -1
- package/types/bundle/strategies/openapi-3-1/visitor.d.ts +59 -3
- package/types/bundle/util.d.ts +39 -0
- package/types/dereference/strategies/arazzo-1/selectors/$anchor.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/selectors/uri.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/util.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +17 -1
- package/types/dereference/strategies/openapi-3-1/selectors/$anchor.d.ts +8 -0
- package/types/dereference/strategies/openapi-3-1/selectors/json-pointer.d.ts +9 -0
- package/types/dereference/strategies/openapi-3-1/selectors/uri.d.ts +37 -4
- package/types/dereference/strategies/openapi-3-1/util.d.ts +53 -2
- package/types/dereference/strategies/openapi-3-1/visitor.d.ts +17 -1
- package/types/util/url.d.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speclynx/apidom-reference",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.5",
|
|
4
4
|
"description": "Advanced algorithms for semantic ApiDOM manipulations like dereferencing or resolution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apidom",
|
|
@@ -299,32 +299,32 @@
|
|
|
299
299
|
"license": "Apache-2.0",
|
|
300
300
|
"dependencies": {
|
|
301
301
|
"@babel/runtime-corejs3": "^8.0.0",
|
|
302
|
-
"@speclynx/apidom-core": "5.2.
|
|
303
|
-
"@speclynx/apidom-datamodel": "5.2.
|
|
304
|
-
"@speclynx/apidom-error": "5.2.
|
|
305
|
-
"@speclynx/apidom-json-pointer": "5.2.
|
|
306
|
-
"@speclynx/apidom-ns-arazzo-1": "5.2.
|
|
307
|
-
"@speclynx/apidom-ns-asyncapi-2": "5.2.
|
|
308
|
-
"@speclynx/apidom-ns-json-schema-2020-12": "5.2.
|
|
309
|
-
"@speclynx/apidom-ns-openapi-2": "5.2.
|
|
310
|
-
"@speclynx/apidom-ns-openapi-3-0": "5.2.
|
|
311
|
-
"@speclynx/apidom-ns-openapi-3-1": "5.2.
|
|
312
|
-
"@speclynx/apidom-ns-overlay-1": "5.2.
|
|
313
|
-
"@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.
|
|
314
|
-
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.
|
|
315
|
-
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.
|
|
316
|
-
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.
|
|
317
|
-
"@speclynx/apidom-parser-adapter-json": "5.2.
|
|
318
|
-
"@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.
|
|
319
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.
|
|
320
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.
|
|
321
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.
|
|
322
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.
|
|
323
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.
|
|
324
|
-
"@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.
|
|
325
|
-
"@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.
|
|
326
|
-
"@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.
|
|
327
|
-
"@speclynx/apidom-traverse": "5.2.
|
|
302
|
+
"@speclynx/apidom-core": "5.2.5",
|
|
303
|
+
"@speclynx/apidom-datamodel": "5.2.5",
|
|
304
|
+
"@speclynx/apidom-error": "5.2.5",
|
|
305
|
+
"@speclynx/apidom-json-pointer": "5.2.5",
|
|
306
|
+
"@speclynx/apidom-ns-arazzo-1": "5.2.5",
|
|
307
|
+
"@speclynx/apidom-ns-asyncapi-2": "5.2.5",
|
|
308
|
+
"@speclynx/apidom-ns-json-schema-2020-12": "5.2.5",
|
|
309
|
+
"@speclynx/apidom-ns-openapi-2": "5.2.5",
|
|
310
|
+
"@speclynx/apidom-ns-openapi-3-0": "5.2.5",
|
|
311
|
+
"@speclynx/apidom-ns-openapi-3-1": "5.2.5",
|
|
312
|
+
"@speclynx/apidom-ns-overlay-1": "5.2.5",
|
|
313
|
+
"@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.5",
|
|
314
|
+
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.5",
|
|
315
|
+
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.5",
|
|
316
|
+
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.5",
|
|
317
|
+
"@speclynx/apidom-parser-adapter-json": "5.2.5",
|
|
318
|
+
"@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.5",
|
|
319
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.5",
|
|
320
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.5",
|
|
321
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.5",
|
|
322
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.5",
|
|
323
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.5",
|
|
324
|
+
"@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.5",
|
|
325
|
+
"@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.5",
|
|
326
|
+
"@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.5",
|
|
327
|
+
"@speclynx/apidom-traverse": "5.2.5",
|
|
328
328
|
"@swaggerexpert/arazzo-runtime-expression": "^3.2.0",
|
|
329
329
|
"axios": "^1.20.0",
|
|
330
330
|
"picomatch": "^4.0.7",
|
|
@@ -346,5 +346,5 @@
|
|
|
346
346
|
"README.md",
|
|
347
347
|
"CHANGELOG.md"
|
|
348
348
|
],
|
|
349
|
-
"gitHead": "
|
|
349
|
+
"gitHead": "677f42e6af4e7b5311c74cd054283c943f8700a9"
|
|
350
350
|
}
|
|
@@ -34,6 +34,12 @@ const cf = (0, _apidomCore.fixedFields)(_apidomNsArazzo.ComponentsElement, {
|
|
|
34
34
|
indexed: true
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A bundled JSON Schema resource awaiting placement into the entry document's
|
|
39
|
+
* `components.inputs`.
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
|
|
37
43
|
/**
|
|
38
44
|
* @public
|
|
39
45
|
*/
|
|
@@ -81,17 +87,47 @@ class Arazzo1BundleVisitor {
|
|
|
81
87
|
* components that are still being bundled.
|
|
82
88
|
*/
|
|
83
89
|
reservedNames;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Embedded resources waiting to be placed into the entry document's
|
|
93
|
+
* `components.inputs`, shared across the entry document and every external
|
|
94
|
+
* document visitor. Placement is deferred until the entry document traversal
|
|
95
|
+
* completes: the traversal walks live content, so placing a resource mid-way
|
|
96
|
+
* would make it descend into the resource and re-bundle its `$ref`s against
|
|
97
|
+
* the entry document's base URI instead of the resource's own.
|
|
98
|
+
*/
|
|
99
|
+
placements;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Index of `$id`-bearing schemas per document, shared across the entry document
|
|
103
|
+
* and every external document visitor of a single bundle run so it never
|
|
104
|
+
* outlives mutations of the documents (e.g. the final placement of inputs).
|
|
105
|
+
*/
|
|
106
|
+
schema$idIndex;
|
|
84
107
|
constructor({
|
|
85
108
|
reference,
|
|
86
109
|
options,
|
|
87
110
|
assignments = new Map(),
|
|
88
|
-
reservedNames = new Map()
|
|
111
|
+
reservedNames = new Map(),
|
|
112
|
+
placements = [],
|
|
113
|
+
schema$idIndex = new WeakMap()
|
|
89
114
|
}) {
|
|
90
115
|
this.reference = reference;
|
|
91
116
|
this.baseURI = (0, _util.resolveArazzo$selfField)(reference.uri, reference.value?.result);
|
|
92
117
|
this.options = options;
|
|
93
118
|
this.assignments = assignments;
|
|
94
119
|
this.reservedNames = reservedNames;
|
|
120
|
+
this.placements = placements;
|
|
121
|
+
this.schema$idIndex = schema$idIndex;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Base URI of the entry document — its `$self` when present, otherwise its
|
|
126
|
+
* retrieval URI. It is the base against which a relative `$id` written into
|
|
127
|
+
* `components.inputs` resolves.
|
|
128
|
+
*/
|
|
129
|
+
get entryBaseURI() {
|
|
130
|
+
return (0, _util.resolveArazzo$selfField)(url.stripHash(this.reference.refSet.rootRef.uri), this.entryResult);
|
|
95
131
|
}
|
|
96
132
|
toBaseURI(uri) {
|
|
97
133
|
return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
|
|
@@ -243,14 +279,17 @@ class Arazzo1BundleVisitor {
|
|
|
243
279
|
|
|
244
280
|
/**
|
|
245
281
|
* Computes a collision-free component name within `components.inputs`. A name
|
|
246
|
-
* is taken if it's already
|
|
247
|
-
* still being bundled (reserved before recursion).
|
|
282
|
+
* is taken if it's already present in the entry document's inputs or reserved
|
|
283
|
+
* by a component that is still being bundled (reserved before recursion).
|
|
248
284
|
*/
|
|
249
285
|
uniqueName(candidate) {
|
|
250
286
|
const field = cf.inputs.name;
|
|
251
|
-
const
|
|
287
|
+
const {
|
|
288
|
+
components
|
|
289
|
+
} = this.entryResult;
|
|
290
|
+
const inputs = (0, _apidomDatamodel.isObjectElement)(components) ? components.get(field) : undefined;
|
|
252
291
|
const reserved = this.reservedNames.get(field) ?? new Set();
|
|
253
|
-
return (0, _util2.uniqueName)(candidate, name => inputs.hasKey(name) || reserved.has(name));
|
|
292
|
+
return (0, _util2.uniqueName)(candidate, name => (0, _apidomDatamodel.isObjectElement)(inputs) && inputs.hasKey(name) || reserved.has(name));
|
|
254
293
|
}
|
|
255
294
|
|
|
256
295
|
/**
|
|
@@ -275,14 +314,36 @@ class Arazzo1BundleVisitor {
|
|
|
275
314
|
this.entryParseResult.content.push(annotation);
|
|
276
315
|
}
|
|
277
316
|
|
|
317
|
+
/**
|
|
318
|
+
* The entry document traversal is rooted at its Parse Result (nested
|
|
319
|
+
* traversals are rooted at schema resources), so leaving it marks the moment
|
|
320
|
+
* the whole entry document has been visited and the collected resources can
|
|
321
|
+
* be placed into `components.inputs` without the traversal descending into them.
|
|
322
|
+
* Guarded on the node so that a traversal rooted at any other Parse Result
|
|
323
|
+
* never flushes the shared placements early.
|
|
324
|
+
*/
|
|
325
|
+
ParseResultElement = {
|
|
326
|
+
leave: path => {
|
|
327
|
+
if (path.node !== this.entryParseResult) return;
|
|
328
|
+
for (const {
|
|
329
|
+
name,
|
|
330
|
+
element
|
|
331
|
+
} of this.placements) {
|
|
332
|
+
this.ensureInputsField().set(name, element);
|
|
333
|
+
}
|
|
334
|
+
this.placements.length = 0;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
278
338
|
/**
|
|
279
339
|
* JSON Schema Objects in Arazzo (used by Input Objects and inline schemas) are
|
|
280
340
|
* a JSON Schema 2020-12 dialect — the only external reference type Arazzo
|
|
281
341
|
* defines. They are bundled per the JSON Schema Compound Document rules: the
|
|
282
342
|
* external schema RESOURCE is embedded verbatim into `components.inputs`
|
|
283
|
-
* (carrying its `$id`), and the referencing `$ref` is
|
|
284
|
-
*
|
|
285
|
-
*
|
|
343
|
+
* (carrying its `$id`), and the referencing `$ref` is rebased onto that `$id`
|
|
344
|
+
* when it resolves to a different URI (a location-based `$ref` to a
|
|
345
|
+
* self-identifying resource); otherwise it is left unchanged. This mirrors
|
|
346
|
+
* the openapi-3-1 strategy's Schema Object handling.
|
|
286
347
|
*/
|
|
287
348
|
async JSONSchemaElement(path) {
|
|
288
349
|
const referencingElement = path.node;
|
|
@@ -297,7 +358,8 @@ class Arazzo1BundleVisitor {
|
|
|
297
358
|
// current document's base URI is derived directly (not via toReference)
|
|
298
359
|
// so an internal $ref needs no resolution and never trips the
|
|
299
360
|
// resolve.maxDepth guard.
|
|
300
|
-
const
|
|
361
|
+
const schemaBaseURI = (0, _util.resolveSchemaBaseURI)(this.baseURI, path);
|
|
362
|
+
const $refBaseURI = (0, _util.resolveSchema$refField)(schemaBaseURI, referencingElement);
|
|
301
363
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
302
364
|
const file = new _File.default({
|
|
303
365
|
uri: $refBaseURIStrippedHash
|
|
@@ -335,7 +397,10 @@ class Arazzo1BundleVisitor {
|
|
|
335
397
|
let schemaReference = this.reference;
|
|
336
398
|
try {
|
|
337
399
|
const referenceAsSchema = (0, _util.maybeRefractToJSONSchemaElement)(schemaReference.value.result);
|
|
338
|
-
(0, _uri.evaluate)($refBaseURI, referenceAsSchema
|
|
400
|
+
(0, _uri.evaluate)($refBaseURI, referenceAsSchema, {
|
|
401
|
+
baseURI: this.baseURI,
|
|
402
|
+
index: this.schema$idIndex
|
|
403
|
+
});
|
|
339
404
|
} catch (error) {
|
|
340
405
|
if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) {
|
|
341
406
|
if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) {
|
|
@@ -374,20 +439,23 @@ class Arazzo1BundleVisitor {
|
|
|
374
439
|
const field = cf.inputs.name;
|
|
375
440
|
const canonicalKey = `${field}\t${resourceBaseURI}`;
|
|
376
441
|
|
|
377
|
-
//
|
|
378
|
-
// $
|
|
442
|
+
// a location-based $ref to a self-identifying resource is rebased onto the
|
|
443
|
+
// resource's $id (keeping its fragment) — once embedded, the resource is
|
|
444
|
+
// reachable only by its $id. A $ref already resolving to that $id is left
|
|
445
|
+
// as written. See rebaseSchema$ref for the spec trade-off.
|
|
446
|
+
const rebased$ref = (0, _util2.rebaseSchema$ref)($refBaseURI, resourceBaseURI);
|
|
447
|
+
|
|
448
|
+
// resource already embedded (or being embedded) — point the referencing
|
|
449
|
+
// $ref at it and stop. This also terminates circular external schema
|
|
379
450
|
// references, since the assignment is reserved BEFORE recursion.
|
|
380
451
|
if (this.assignments.has(canonicalKey)) {
|
|
452
|
+
if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
|
|
381
453
|
path.skip();
|
|
382
454
|
return;
|
|
383
455
|
}
|
|
384
456
|
|
|
385
|
-
// own a copy of the resource
|
|
386
|
-
// $ref still resolves against it once embedded
|
|
457
|
+
// own a copy of the resource
|
|
387
458
|
const embeddedElement = (0, _apidomDatamodel.cloneDeep)(resourceRoot);
|
|
388
|
-
if (!(0, _apidomDatamodel.isStringElement)(embeddedElement.$id)) {
|
|
389
|
-
embeddedElement.set('$id', resourceBaseURI);
|
|
390
|
-
}
|
|
391
459
|
|
|
392
460
|
// the embedded thing is the WHOLE resource (keyed by its $id), so its name
|
|
393
461
|
// is derived from the resource URI
|
|
@@ -407,22 +475,37 @@ class Arazzo1BundleVisitor {
|
|
|
407
475
|
reference: schemaReference,
|
|
408
476
|
options: this.options,
|
|
409
477
|
assignments: this.assignments,
|
|
410
|
-
reservedNames: this.reservedNames
|
|
478
|
+
reservedNames: this.reservedNames,
|
|
479
|
+
placements: this.placements,
|
|
480
|
+
schema$idIndex: this.schema$idIndex
|
|
411
481
|
});
|
|
412
482
|
const bundledElement = await (0, _apidomTraverse.traverseAsync)(embeddedElement, visitor, {
|
|
413
483
|
mutable: true
|
|
414
484
|
});
|
|
415
485
|
|
|
486
|
+
// a resource without its own $id is identified by its retrieval URI; write
|
|
487
|
+
// it relative to the entry document (the base of components.inputs) so the
|
|
488
|
+
// bundle carries no absolute filesystem path and stays relocatable. Assigned
|
|
489
|
+
// after the nested traversal so a reader deriving a schema's base from live
|
|
490
|
+
// $ids (see #540) never resolves the resource's own $refs against the entry
|
|
491
|
+
// document.
|
|
492
|
+
if (!(0, _apidomDatamodel.isStringElement)(bundledElement.$id)) {
|
|
493
|
+
bundledElement.set('$id', url.relative(this.entryBaseURI, resourceBaseURI));
|
|
494
|
+
}
|
|
495
|
+
|
|
416
496
|
// annotate the embedded resource with info about its origin
|
|
417
497
|
if ((0, _apidomDatamodel.isElement)(bundledElement)) {
|
|
418
498
|
bundledElement.meta.set('ref-origin', schemaReference.uri);
|
|
419
499
|
}
|
|
420
500
|
|
|
421
|
-
//
|
|
422
|
-
this.
|
|
501
|
+
// queue the embedded resource for placement into the entry document's components.inputs
|
|
502
|
+
this.placements.push({
|
|
503
|
+
name: componentName,
|
|
504
|
+
element: bundledElement
|
|
505
|
+
});
|
|
423
506
|
|
|
424
|
-
// the referencing $ref
|
|
425
|
-
|
|
507
|
+
// point the referencing $ref at the embedded resource's $id
|
|
508
|
+
if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
|
|
426
509
|
path.skip();
|
|
427
510
|
} catch (error) {
|
|
428
511
|
this.handleError(`Error while bundling JSON Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
@@ -15,8 +15,8 @@ import Reference from "../../../Reference.mjs";
|
|
|
15
15
|
import File from "../../../File.mjs";
|
|
16
16
|
import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "../../../dereference/strategies/arazzo-1/selectors/$anchor.mjs";
|
|
17
17
|
import { evaluate as uriEvaluate } from "../../../dereference/strategies/arazzo-1/selectors/uri.mjs";
|
|
18
|
-
import { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "../../../dereference/strategies/arazzo-1/util.mjs";
|
|
19
|
-
import {
|
|
18
|
+
import { resolveSchema$refField, resolveSchema$idField, resolveSchemaBaseURI, maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "../../../dereference/strategies/arazzo-1/util.mjs";
|
|
19
|
+
import { rebaseSchema$ref, sanitizeComponentName, toPascalCase, uniqueName as resolveUniqueName } from "../../util.mjs";
|
|
20
20
|
/**
|
|
21
21
|
* The Components Object field where embedded JSON Schema resources are hoisted.
|
|
22
22
|
* Sourced from the Components Object fixed fields so it cannot drift from the
|
|
@@ -26,6 +26,12 @@ const cf = fixedFields(ComponentsElement, {
|
|
|
26
26
|
indexed: true
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A bundled JSON Schema resource awaiting placement into the entry document's
|
|
31
|
+
* `components.inputs`.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
|
|
29
35
|
/**
|
|
30
36
|
* @public
|
|
31
37
|
*/
|
|
@@ -73,17 +79,47 @@ class Arazzo1BundleVisitor {
|
|
|
73
79
|
* components that are still being bundled.
|
|
74
80
|
*/
|
|
75
81
|
reservedNames;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Embedded resources waiting to be placed into the entry document's
|
|
85
|
+
* `components.inputs`, shared across the entry document and every external
|
|
86
|
+
* document visitor. Placement is deferred until the entry document traversal
|
|
87
|
+
* completes: the traversal walks live content, so placing a resource mid-way
|
|
88
|
+
* would make it descend into the resource and re-bundle its `$ref`s against
|
|
89
|
+
* the entry document's base URI instead of the resource's own.
|
|
90
|
+
*/
|
|
91
|
+
placements;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Index of `$id`-bearing schemas per document, shared across the entry document
|
|
95
|
+
* and every external document visitor of a single bundle run so it never
|
|
96
|
+
* outlives mutations of the documents (e.g. the final placement of inputs).
|
|
97
|
+
*/
|
|
98
|
+
schema$idIndex;
|
|
76
99
|
constructor({
|
|
77
100
|
reference,
|
|
78
101
|
options,
|
|
79
102
|
assignments = new Map(),
|
|
80
|
-
reservedNames = new Map()
|
|
103
|
+
reservedNames = new Map(),
|
|
104
|
+
placements = [],
|
|
105
|
+
schema$idIndex = new WeakMap()
|
|
81
106
|
}) {
|
|
82
107
|
this.reference = reference;
|
|
83
108
|
this.baseURI = resolveArazzo$selfField(reference.uri, reference.value?.result);
|
|
84
109
|
this.options = options;
|
|
85
110
|
this.assignments = assignments;
|
|
86
111
|
this.reservedNames = reservedNames;
|
|
112
|
+
this.placements = placements;
|
|
113
|
+
this.schema$idIndex = schema$idIndex;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Base URI of the entry document — its `$self` when present, otherwise its
|
|
118
|
+
* retrieval URI. It is the base against which a relative `$id` written into
|
|
119
|
+
* `components.inputs` resolves.
|
|
120
|
+
*/
|
|
121
|
+
get entryBaseURI() {
|
|
122
|
+
return resolveArazzo$selfField(url.stripHash(this.reference.refSet.rootRef.uri), this.entryResult);
|
|
87
123
|
}
|
|
88
124
|
toBaseURI(uri) {
|
|
89
125
|
return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
|
|
@@ -235,14 +271,17 @@ class Arazzo1BundleVisitor {
|
|
|
235
271
|
|
|
236
272
|
/**
|
|
237
273
|
* Computes a collision-free component name within `components.inputs`. A name
|
|
238
|
-
* is taken if it's already
|
|
239
|
-
* still being bundled (reserved before recursion).
|
|
274
|
+
* is taken if it's already present in the entry document's inputs or reserved
|
|
275
|
+
* by a component that is still being bundled (reserved before recursion).
|
|
240
276
|
*/
|
|
241
277
|
uniqueName(candidate) {
|
|
242
278
|
const field = cf.inputs.name;
|
|
243
|
-
const
|
|
279
|
+
const {
|
|
280
|
+
components
|
|
281
|
+
} = this.entryResult;
|
|
282
|
+
const inputs = isObjectElement(components) ? components.get(field) : undefined;
|
|
244
283
|
const reserved = this.reservedNames.get(field) ?? new Set();
|
|
245
|
-
return resolveUniqueName(candidate, name => inputs.hasKey(name) || reserved.has(name));
|
|
284
|
+
return resolveUniqueName(candidate, name => isObjectElement(inputs) && inputs.hasKey(name) || reserved.has(name));
|
|
246
285
|
}
|
|
247
286
|
|
|
248
287
|
/**
|
|
@@ -267,14 +306,36 @@ class Arazzo1BundleVisitor {
|
|
|
267
306
|
this.entryParseResult.content.push(annotation);
|
|
268
307
|
}
|
|
269
308
|
|
|
309
|
+
/**
|
|
310
|
+
* The entry document traversal is rooted at its Parse Result (nested
|
|
311
|
+
* traversals are rooted at schema resources), so leaving it marks the moment
|
|
312
|
+
* the whole entry document has been visited and the collected resources can
|
|
313
|
+
* be placed into `components.inputs` without the traversal descending into them.
|
|
314
|
+
* Guarded on the node so that a traversal rooted at any other Parse Result
|
|
315
|
+
* never flushes the shared placements early.
|
|
316
|
+
*/
|
|
317
|
+
ParseResultElement = {
|
|
318
|
+
leave: path => {
|
|
319
|
+
if (path.node !== this.entryParseResult) return;
|
|
320
|
+
for (const {
|
|
321
|
+
name,
|
|
322
|
+
element
|
|
323
|
+
} of this.placements) {
|
|
324
|
+
this.ensureInputsField().set(name, element);
|
|
325
|
+
}
|
|
326
|
+
this.placements.length = 0;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
270
330
|
/**
|
|
271
331
|
* JSON Schema Objects in Arazzo (used by Input Objects and inline schemas) are
|
|
272
332
|
* a JSON Schema 2020-12 dialect — the only external reference type Arazzo
|
|
273
333
|
* defines. They are bundled per the JSON Schema Compound Document rules: the
|
|
274
334
|
* external schema RESOURCE is embedded verbatim into `components.inputs`
|
|
275
|
-
* (carrying its `$id`), and the referencing `$ref` is
|
|
276
|
-
*
|
|
277
|
-
*
|
|
335
|
+
* (carrying its `$id`), and the referencing `$ref` is rebased onto that `$id`
|
|
336
|
+
* when it resolves to a different URI (a location-based `$ref` to a
|
|
337
|
+
* self-identifying resource); otherwise it is left unchanged. This mirrors
|
|
338
|
+
* the openapi-3-1 strategy's Schema Object handling.
|
|
278
339
|
*/
|
|
279
340
|
async JSONSchemaElement(path) {
|
|
280
341
|
const referencingElement = path.node;
|
|
@@ -289,7 +350,8 @@ class Arazzo1BundleVisitor {
|
|
|
289
350
|
// current document's base URI is derived directly (not via toReference)
|
|
290
351
|
// so an internal $ref needs no resolution and never trips the
|
|
291
352
|
// resolve.maxDepth guard.
|
|
292
|
-
const
|
|
353
|
+
const schemaBaseURI = resolveSchemaBaseURI(this.baseURI, path);
|
|
354
|
+
const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
|
|
293
355
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
294
356
|
const file = new File({
|
|
295
357
|
uri: $refBaseURIStrippedHash
|
|
@@ -327,7 +389,10 @@ class Arazzo1BundleVisitor {
|
|
|
327
389
|
let schemaReference = this.reference;
|
|
328
390
|
try {
|
|
329
391
|
const referenceAsSchema = maybeRefractToJSONSchemaElement(schemaReference.value.result);
|
|
330
|
-
uriEvaluate($refBaseURI, referenceAsSchema
|
|
392
|
+
uriEvaluate($refBaseURI, referenceAsSchema, {
|
|
393
|
+
baseURI: this.baseURI,
|
|
394
|
+
index: this.schema$idIndex
|
|
395
|
+
});
|
|
331
396
|
} catch (error) {
|
|
332
397
|
if (isURL && error instanceof EvaluationJsonSchemaUriError) {
|
|
333
398
|
if (isAnchor(uriToAnchor($refBaseURI))) {
|
|
@@ -366,20 +431,23 @@ class Arazzo1BundleVisitor {
|
|
|
366
431
|
const field = cf.inputs.name;
|
|
367
432
|
const canonicalKey = `${field}\t${resourceBaseURI}`;
|
|
368
433
|
|
|
369
|
-
//
|
|
370
|
-
// $
|
|
434
|
+
// a location-based $ref to a self-identifying resource is rebased onto the
|
|
435
|
+
// resource's $id (keeping its fragment) — once embedded, the resource is
|
|
436
|
+
// reachable only by its $id. A $ref already resolving to that $id is left
|
|
437
|
+
// as written. See rebaseSchema$ref for the spec trade-off.
|
|
438
|
+
const rebased$ref = rebaseSchema$ref($refBaseURI, resourceBaseURI);
|
|
439
|
+
|
|
440
|
+
// resource already embedded (or being embedded) — point the referencing
|
|
441
|
+
// $ref at it and stop. This also terminates circular external schema
|
|
371
442
|
// references, since the assignment is reserved BEFORE recursion.
|
|
372
443
|
if (this.assignments.has(canonicalKey)) {
|
|
444
|
+
if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
|
|
373
445
|
path.skip();
|
|
374
446
|
return;
|
|
375
447
|
}
|
|
376
448
|
|
|
377
|
-
// own a copy of the resource
|
|
378
|
-
// $ref still resolves against it once embedded
|
|
449
|
+
// own a copy of the resource
|
|
379
450
|
const embeddedElement = cloneDeep(resourceRoot);
|
|
380
|
-
if (!isStringElement(embeddedElement.$id)) {
|
|
381
|
-
embeddedElement.set('$id', resourceBaseURI);
|
|
382
|
-
}
|
|
383
451
|
|
|
384
452
|
// the embedded thing is the WHOLE resource (keyed by its $id), so its name
|
|
385
453
|
// is derived from the resource URI
|
|
@@ -399,22 +467,37 @@ class Arazzo1BundleVisitor {
|
|
|
399
467
|
reference: schemaReference,
|
|
400
468
|
options: this.options,
|
|
401
469
|
assignments: this.assignments,
|
|
402
|
-
reservedNames: this.reservedNames
|
|
470
|
+
reservedNames: this.reservedNames,
|
|
471
|
+
placements: this.placements,
|
|
472
|
+
schema$idIndex: this.schema$idIndex
|
|
403
473
|
});
|
|
404
474
|
const bundledElement = await traverseAsync(embeddedElement, visitor, {
|
|
405
475
|
mutable: true
|
|
406
476
|
});
|
|
407
477
|
|
|
478
|
+
// a resource without its own $id is identified by its retrieval URI; write
|
|
479
|
+
// it relative to the entry document (the base of components.inputs) so the
|
|
480
|
+
// bundle carries no absolute filesystem path and stays relocatable. Assigned
|
|
481
|
+
// after the nested traversal so a reader deriving a schema's base from live
|
|
482
|
+
// $ids (see #540) never resolves the resource's own $refs against the entry
|
|
483
|
+
// document.
|
|
484
|
+
if (!isStringElement(bundledElement.$id)) {
|
|
485
|
+
bundledElement.set('$id', url.relative(this.entryBaseURI, resourceBaseURI));
|
|
486
|
+
}
|
|
487
|
+
|
|
408
488
|
// annotate the embedded resource with info about its origin
|
|
409
489
|
if (isElement(bundledElement)) {
|
|
410
490
|
bundledElement.meta.set('ref-origin', schemaReference.uri);
|
|
411
491
|
}
|
|
412
492
|
|
|
413
|
-
//
|
|
414
|
-
this.
|
|
493
|
+
// queue the embedded resource for placement into the entry document's components.inputs
|
|
494
|
+
this.placements.push({
|
|
495
|
+
name: componentName,
|
|
496
|
+
element: bundledElement
|
|
497
|
+
});
|
|
415
498
|
|
|
416
|
-
// the referencing $ref
|
|
417
|
-
|
|
499
|
+
// point the referencing $ref at the embedded resource's $id
|
|
500
|
+
if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
|
|
418
501
|
path.skip();
|
|
419
502
|
} catch (error) {
|
|
420
503
|
this.handleError(`Error while bundling JSON Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|