@speclynx/apidom-reference 5.1.1 → 5.2.2
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 +16 -0
- package/README.md +44 -2
- package/dist/apidom-reference.browser.js +2095 -777
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +31 -31
- package/src/bundle/strategies/arazzo-1/visitor.cjs +24 -8
- package/src/bundle/strategies/arazzo-1/visitor.mjs +25 -9
- package/src/dereference/strategies/arazzo-1/index.cjs +12 -0
- package/src/dereference/strategies/arazzo-1/index.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/source-descriptions.cjs +43 -41
- package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +43 -41
- package/src/dereference/strategies/arazzo-1/util.cjs +86 -1
- package/src/dereference/strategies/arazzo-1/util.mjs +81 -2
- package/src/dereference/strategies/arazzo-1/visitor.cjs +21 -7
- package/src/dereference/strategies/arazzo-1/visitor.mjs +22 -8
- package/src/parse/parsers/arazzo-json-1/source-descriptions.cjs +39 -38
- package/src/parse/parsers/arazzo-json-1/source-descriptions.mjs +40 -39
- package/types/bundle/strategies/arazzo-1/visitor.d.ts +6 -0
- package/types/dereference/strategies/arazzo-1/index.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/source-descriptions.d.ts +7 -0
- package/types/dereference/strategies/arazzo-1/util.d.ts +34 -1
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +6 -0
- package/types/parse/parsers/arazzo-json-1/source-descriptions.d.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speclynx/apidom-reference",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.2",
|
|
4
4
|
"description": "Advanced algorithms for semantic ApiDOM manipulations like dereferencing or resolution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apidom",
|
|
@@ -299,35 +299,35 @@
|
|
|
299
299
|
"license": "Apache-2.0",
|
|
300
300
|
"dependencies": {
|
|
301
301
|
"@babel/runtime-corejs3": "^8.0.0",
|
|
302
|
-
"@speclynx/apidom-core": "5.
|
|
303
|
-
"@speclynx/apidom-datamodel": "5.
|
|
304
|
-
"@speclynx/apidom-error": "5.
|
|
305
|
-
"@speclynx/apidom-json-pointer": "5.
|
|
306
|
-
"@speclynx/apidom-ns-arazzo-1": "5.
|
|
307
|
-
"@speclynx/apidom-ns-asyncapi-2": "5.
|
|
308
|
-
"@speclynx/apidom-ns-json-schema-2020-12": "5.
|
|
309
|
-
"@speclynx/apidom-ns-openapi-2": "5.
|
|
310
|
-
"@speclynx/apidom-ns-openapi-3-0": "5.
|
|
311
|
-
"@speclynx/apidom-ns-openapi-3-1": "5.
|
|
312
|
-
"@speclynx/apidom-ns-overlay-1": "5.
|
|
313
|
-
"@speclynx/apidom-parser-adapter-arazzo-json-1": "5.
|
|
314
|
-
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.
|
|
315
|
-
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.
|
|
316
|
-
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.
|
|
317
|
-
"@speclynx/apidom-parser-adapter-json": "5.
|
|
318
|
-
"@speclynx/apidom-parser-adapter-openapi-json-2": "5.
|
|
319
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.
|
|
320
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.
|
|
321
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.
|
|
322
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.
|
|
323
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.
|
|
324
|
-
"@speclynx/apidom-parser-adapter-overlay-json-1": "5.
|
|
325
|
-
"@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.
|
|
326
|
-
"@speclynx/apidom-parser-adapter-yaml-1-2": "5.
|
|
327
|
-
"@speclynx/apidom-traverse": "5.
|
|
328
|
-
"@swaggerexpert/arazzo-runtime-expression": "^3.
|
|
329
|
-
"axios": "^1.
|
|
330
|
-
"picomatch": "^4.0.
|
|
302
|
+
"@speclynx/apidom-core": "5.2.2",
|
|
303
|
+
"@speclynx/apidom-datamodel": "5.2.2",
|
|
304
|
+
"@speclynx/apidom-error": "5.2.2",
|
|
305
|
+
"@speclynx/apidom-json-pointer": "5.2.2",
|
|
306
|
+
"@speclynx/apidom-ns-arazzo-1": "5.2.2",
|
|
307
|
+
"@speclynx/apidom-ns-asyncapi-2": "5.2.2",
|
|
308
|
+
"@speclynx/apidom-ns-json-schema-2020-12": "5.2.2",
|
|
309
|
+
"@speclynx/apidom-ns-openapi-2": "5.2.2",
|
|
310
|
+
"@speclynx/apidom-ns-openapi-3-0": "5.2.2",
|
|
311
|
+
"@speclynx/apidom-ns-openapi-3-1": "5.2.2",
|
|
312
|
+
"@speclynx/apidom-ns-overlay-1": "5.2.2",
|
|
313
|
+
"@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.2",
|
|
314
|
+
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.2",
|
|
315
|
+
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.2",
|
|
316
|
+
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.2",
|
|
317
|
+
"@speclynx/apidom-parser-adapter-json": "5.2.2",
|
|
318
|
+
"@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.2",
|
|
319
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.2",
|
|
320
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.2",
|
|
321
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.2",
|
|
322
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.2",
|
|
323
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.2",
|
|
324
|
+
"@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.2",
|
|
325
|
+
"@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.2",
|
|
326
|
+
"@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.2",
|
|
327
|
+
"@speclynx/apidom-traverse": "5.2.2",
|
|
328
|
+
"@swaggerexpert/arazzo-runtime-expression": "^3.2.0",
|
|
329
|
+
"axios": "^1.20.0",
|
|
330
|
+
"picomatch": "^4.0.7",
|
|
331
331
|
"process": "^0.11.10",
|
|
332
332
|
"ramda": "~0.32.0",
|
|
333
333
|
"ramda-adjunct": "^6.1.0"
|
|
@@ -346,5 +346,5 @@
|
|
|
346
346
|
"README.md",
|
|
347
347
|
"CHANGELOG.md"
|
|
348
348
|
],
|
|
349
|
-
"gitHead": "
|
|
349
|
+
"gitHead": "2d0e035bf82913a9870ff7b26069e784e2a0e9a3"
|
|
350
350
|
}
|
|
@@ -43,6 +43,13 @@ const cf = (0, _apidomCore.fixedFields)(_apidomNsArazzo.ComponentsElement, {
|
|
|
43
43
|
*/
|
|
44
44
|
class Arazzo1BundleVisitor {
|
|
45
45
|
reference;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Base URI of the current document for resolving relative references.
|
|
49
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back
|
|
50
|
+
* to the retrieval URI (`this.reference.uri`).
|
|
51
|
+
*/
|
|
52
|
+
baseURI;
|
|
46
53
|
options;
|
|
47
54
|
|
|
48
55
|
/**
|
|
@@ -81,12 +88,13 @@ class Arazzo1BundleVisitor {
|
|
|
81
88
|
reservedNames = new Map()
|
|
82
89
|
}) {
|
|
83
90
|
this.reference = reference;
|
|
91
|
+
this.baseURI = (0, _util.resolveArazzo$selfField)(reference.uri, reference.value?.result);
|
|
84
92
|
this.options = options;
|
|
85
93
|
this.assignments = assignments;
|
|
86
94
|
this.reservedNames = reservedNames;
|
|
87
95
|
}
|
|
88
96
|
toBaseURI(uri) {
|
|
89
|
-
return url.resolve(this.
|
|
97
|
+
return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
|
|
90
98
|
}
|
|
91
99
|
async toReference(uri) {
|
|
92
100
|
// detect maximum depth of resolution
|
|
@@ -105,6 +113,12 @@ class Arazzo1BundleVisitor {
|
|
|
105
113
|
if (refSet.has(baseURI)) {
|
|
106
114
|
return refSet.find(ref => ref.uri === baseURI);
|
|
107
115
|
}
|
|
116
|
+
|
|
117
|
+
// identity-based referencing: URI matches `$self` of an already processed Arazzo document
|
|
118
|
+
const referenceBy$self = refSet.find((0, _util.identifiedBy$self)(baseURI));
|
|
119
|
+
if (referenceBy$self !== undefined) {
|
|
120
|
+
return referenceBy$self;
|
|
121
|
+
}
|
|
108
122
|
const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
|
|
109
123
|
...this.options,
|
|
110
124
|
parse: {
|
|
@@ -279,19 +293,18 @@ class Arazzo1BundleVisitor {
|
|
|
279
293
|
}
|
|
280
294
|
const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
|
|
281
295
|
try {
|
|
282
|
-
// compute baseURI using rules around $id and $ref keywords. The
|
|
283
|
-
// document's URI is
|
|
284
|
-
//
|
|
296
|
+
// compute baseURI using rules around $self, $id and $ref keywords. The
|
|
297
|
+
// current document's base URI is derived directly (not via toReference)
|
|
298
|
+
// so an internal $ref needs no resolution and never trips the
|
|
285
299
|
// resolve.maxDepth guard.
|
|
286
|
-
const
|
|
287
|
-
const $refBaseURI = (0, _util.resolveSchema$refField)(retrievalURI, referencingElement);
|
|
300
|
+
const $refBaseURI = (0, _util.resolveSchema$refField)(this.baseURI, referencingElement);
|
|
288
301
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
289
302
|
const file = new _File.default({
|
|
290
303
|
uri: $refBaseURIStrippedHash
|
|
291
304
|
});
|
|
292
305
|
const isUnknownURI = (0, _ramda.none)(r => r.canRead(file), this.options.resolve.resolvers);
|
|
293
306
|
const isURL = !isUnknownURI;
|
|
294
|
-
const isInternalReference =
|
|
307
|
+
const isInternalReference = this.baseURI === $refBaseURIStrippedHash;
|
|
295
308
|
const isExternalReference = !isInternalReference;
|
|
296
309
|
|
|
297
310
|
// internal Schema Object $refs resolve against the in-document $id graph;
|
|
@@ -345,7 +358,10 @@ class Arazzo1BundleVisitor {
|
|
|
345
358
|
// is internal even when its URI form (e.g. a URN or absolute $id) is not a
|
|
346
359
|
// bare fragment. Leave it untouched — it resolves against the in-document
|
|
347
360
|
// $id graph, and embedding the current document into itself would be wrong.
|
|
348
|
-
|
|
361
|
+
// The same holds for a $ref resolving into the entry document (e.g. by its
|
|
362
|
+
// `$self` identity) — it's already part of the bundle.
|
|
363
|
+
const schemaReferenceURI = url.stripHash(schemaReference.uri);
|
|
364
|
+
if (schemaReferenceURI === url.stripHash(this.reference.uri) || schemaReferenceURI === url.stripHash(this.reference.refSet.rootRef.uri)) {
|
|
349
365
|
return;
|
|
350
366
|
}
|
|
351
367
|
|
|
@@ -15,7 +15,7 @@ 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 } from "../../../dereference/strategies/arazzo-1/util.mjs";
|
|
18
|
+
import { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "../../../dereference/strategies/arazzo-1/util.mjs";
|
|
19
19
|
import { toPascalCase, sanitizeComponentName, uniqueName as resolveUniqueName } from "../../util.mjs";
|
|
20
20
|
/**
|
|
21
21
|
* The Components Object field where embedded JSON Schema resources are hoisted.
|
|
@@ -35,6 +35,13 @@ const cf = fixedFields(ComponentsElement, {
|
|
|
35
35
|
*/
|
|
36
36
|
class Arazzo1BundleVisitor {
|
|
37
37
|
reference;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Base URI of the current document for resolving relative references.
|
|
41
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back
|
|
42
|
+
* to the retrieval URI (`this.reference.uri`).
|
|
43
|
+
*/
|
|
44
|
+
baseURI;
|
|
38
45
|
options;
|
|
39
46
|
|
|
40
47
|
/**
|
|
@@ -73,12 +80,13 @@ class Arazzo1BundleVisitor {
|
|
|
73
80
|
reservedNames = new Map()
|
|
74
81
|
}) {
|
|
75
82
|
this.reference = reference;
|
|
83
|
+
this.baseURI = resolveArazzo$selfField(reference.uri, reference.value?.result);
|
|
76
84
|
this.options = options;
|
|
77
85
|
this.assignments = assignments;
|
|
78
86
|
this.reservedNames = reservedNames;
|
|
79
87
|
}
|
|
80
88
|
toBaseURI(uri) {
|
|
81
|
-
return url.resolve(this.
|
|
89
|
+
return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
|
|
82
90
|
}
|
|
83
91
|
async toReference(uri) {
|
|
84
92
|
// detect maximum depth of resolution
|
|
@@ -97,6 +105,12 @@ class Arazzo1BundleVisitor {
|
|
|
97
105
|
if (refSet.has(baseURI)) {
|
|
98
106
|
return refSet.find(ref => ref.uri === baseURI);
|
|
99
107
|
}
|
|
108
|
+
|
|
109
|
+
// identity-based referencing: URI matches `$self` of an already processed Arazzo document
|
|
110
|
+
const referenceBy$self = refSet.find(identifiedBy$self(baseURI));
|
|
111
|
+
if (referenceBy$self !== undefined) {
|
|
112
|
+
return referenceBy$self;
|
|
113
|
+
}
|
|
100
114
|
const parseResult = await parse(url.unsanitize(baseURI), {
|
|
101
115
|
...this.options,
|
|
102
116
|
parse: {
|
|
@@ -271,19 +285,18 @@ class Arazzo1BundleVisitor {
|
|
|
271
285
|
}
|
|
272
286
|
const $ref = toValue(referencingElement.$ref);
|
|
273
287
|
try {
|
|
274
|
-
// compute baseURI using rules around $id and $ref keywords. The
|
|
275
|
-
// document's URI is
|
|
276
|
-
//
|
|
288
|
+
// compute baseURI using rules around $self, $id and $ref keywords. The
|
|
289
|
+
// current document's base URI is derived directly (not via toReference)
|
|
290
|
+
// so an internal $ref needs no resolution and never trips the
|
|
277
291
|
// resolve.maxDepth guard.
|
|
278
|
-
const
|
|
279
|
-
const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement);
|
|
292
|
+
const $refBaseURI = resolveSchema$refField(this.baseURI, referencingElement);
|
|
280
293
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
281
294
|
const file = new File({
|
|
282
295
|
uri: $refBaseURIStrippedHash
|
|
283
296
|
});
|
|
284
297
|
const isUnknownURI = none(r => r.canRead(file), this.options.resolve.resolvers);
|
|
285
298
|
const isURL = !isUnknownURI;
|
|
286
|
-
const isInternalReference =
|
|
299
|
+
const isInternalReference = this.baseURI === $refBaseURIStrippedHash;
|
|
287
300
|
const isExternalReference = !isInternalReference;
|
|
288
301
|
|
|
289
302
|
// internal Schema Object $refs resolve against the in-document $id graph;
|
|
@@ -337,7 +350,10 @@ class Arazzo1BundleVisitor {
|
|
|
337
350
|
// is internal even when its URI form (e.g. a URN or absolute $id) is not a
|
|
338
351
|
// bare fragment. Leave it untouched — it resolves against the in-document
|
|
339
352
|
// $id graph, and embedding the current document into itself would be wrong.
|
|
340
|
-
|
|
353
|
+
// The same holds for a $ref resolving into the entry document (e.g. by its
|
|
354
|
+
// `$self` identity) — it's already part of the bundle.
|
|
355
|
+
const schemaReferenceURI = url.stripHash(schemaReference.uri);
|
|
356
|
+
if (schemaReferenceURI === url.stripHash(this.reference.uri) || schemaReferenceURI === url.stripHash(this.reference.refSet.rootRef.uri)) {
|
|
341
357
|
return;
|
|
342
358
|
}
|
|
343
359
|
|
|
@@ -17,12 +17,24 @@ Object.defineProperty(exports, "dereferenceSourceDescriptions", {
|
|
|
17
17
|
return _sourceDescriptions.dereferenceSourceDescriptions;
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
|
+
Object.defineProperty(exports, "identifiedBy$self", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () {
|
|
23
|
+
return _util.identifiedBy$self;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
20
26
|
Object.defineProperty(exports, "maybeRefractToJSONSchemaElement", {
|
|
21
27
|
enumerable: true,
|
|
22
28
|
get: function () {
|
|
23
29
|
return _util.maybeRefractToJSONSchemaElement;
|
|
24
30
|
}
|
|
25
31
|
});
|
|
32
|
+
Object.defineProperty(exports, "resolveArazzo$selfField", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function () {
|
|
35
|
+
return _util.resolveArazzo$selfField;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
26
38
|
Object.defineProperty(exports, "resolveSchema$idField", {
|
|
27
39
|
enumerable: true,
|
|
28
40
|
get: function () {
|
|
@@ -104,6 +104,6 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
export { Arazzo1DereferenceVisitor };
|
|
107
|
-
export { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement } from "./util.mjs";
|
|
107
|
+
export { resolveSchema$refField, resolveSchema$idField, resolveArazzo$selfField, identifiedBy$self, maybeRefractToJSONSchemaElement } from "./util.mjs";
|
|
108
108
|
export { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
|
|
109
109
|
export default Arazzo1DereferenceStrategy;
|
|
@@ -7,13 +7,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
exports.dereferenceSourceDescriptions = dereferenceSourceDescriptions;
|
|
8
8
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
9
9
|
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
10
|
-
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
11
|
-
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
12
|
-
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
13
10
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
14
11
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
15
12
|
var _util = require("../../../options/util.cjs");
|
|
16
13
|
var _index = _interopRequireWildcard(require("../../index.cjs"));
|
|
14
|
+
var _util2 = require("./util.cjs");
|
|
17
15
|
/**
|
|
18
16
|
* Dereferences a single source description element.
|
|
19
17
|
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
@@ -43,14 +41,25 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
43
41
|
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
44
42
|
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
45
43
|
|
|
46
|
-
// skip if
|
|
47
|
-
if (ctx.
|
|
44
|
+
// skip if the document is still being dereferenced higher up the chain (cycle detection)
|
|
45
|
+
if (ctx.ancestors.includes(retrievalURI)) {
|
|
48
46
|
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
49
47
|
annotation.classes.push('warning');
|
|
50
48
|
parseResult.push(annotation);
|
|
51
49
|
return parseResult;
|
|
52
50
|
}
|
|
53
|
-
|
|
51
|
+
|
|
52
|
+
// a document reached again through a different path is a shared dependency, not a cycle;
|
|
53
|
+
// point at the result where it was dereferenced instead of dereferencing it again
|
|
54
|
+
const sharedDereferenceResult = ctx.dereferenced.get(retrievalURI);
|
|
55
|
+
if (sharedDereferenceResult !== undefined) {
|
|
56
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been dereferenced. Reusing existing dereference result`);
|
|
57
|
+
annotation.classes.push('info');
|
|
58
|
+
parseResult.push(annotation);
|
|
59
|
+
parseResult.meta.set('parseResult', sharedDereferenceResult);
|
|
60
|
+
(0, _util2.validateSourceDescriptionAPI)(parseResult, sourceDescription, sharedDereferenceResult.api, retrievalURI);
|
|
61
|
+
return parseResult;
|
|
62
|
+
}
|
|
54
63
|
|
|
55
64
|
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
56
65
|
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
@@ -73,7 +82,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
73
82
|
[ctx.strategyName]: {
|
|
74
83
|
sourceDescriptions: true,
|
|
75
84
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
76
|
-
|
|
85
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
86
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
77
87
|
}
|
|
78
88
|
}
|
|
79
89
|
}
|
|
@@ -92,7 +102,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
92
102
|
[ctx.strategyName]: {
|
|
93
103
|
sourceDescriptions: true,
|
|
94
104
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
95
|
-
|
|
105
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
106
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
}
|
|
@@ -112,32 +123,14 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
112
123
|
return parseResult;
|
|
113
124
|
}
|
|
114
125
|
|
|
115
|
-
//
|
|
126
|
+
// register dereferenced document for later references to it
|
|
116
127
|
const {
|
|
117
128
|
api: sourceDescriptionAPI
|
|
118
129
|
} = parseResult;
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
if (!isOpenApi && !isArazzo) {
|
|
122
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
123
|
-
annotation.classes.push('warning');
|
|
124
|
-
parseResult.push(annotation);
|
|
125
|
-
return parseResult;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// validate declared type matches actual dereferenced type
|
|
129
|
-
const declaredType = (0, _apidomCore.toValue)(sourceDescription.type);
|
|
130
|
-
if (typeof declaredType === 'string') {
|
|
131
|
-
if (declaredType === 'openapi' && !isOpenApi) {
|
|
132
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but dereferenced as Arazzo document`);
|
|
133
|
-
annotation.classes.push('warning');
|
|
134
|
-
parseResult.push(annotation);
|
|
135
|
-
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
136
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but dereferenced as OpenAPI document`);
|
|
137
|
-
annotation.classes.push('warning');
|
|
138
|
-
parseResult.push(annotation);
|
|
139
|
-
}
|
|
130
|
+
for (const documentURI of (0, _util2.arazzoDocumentURIs)(retrievalURI, sourceDescriptionAPI)) {
|
|
131
|
+
ctx.dereferenced.set(documentURI, parseResult);
|
|
140
132
|
}
|
|
133
|
+
(0, _util2.validateSourceDescriptionAPI)(parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI);
|
|
141
134
|
return parseResult;
|
|
142
135
|
}
|
|
143
136
|
|
|
@@ -149,6 +142,13 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
149
142
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
150
143
|
* contains annotations explaining what went wrong.
|
|
151
144
|
*
|
|
145
|
+
* A document referenced from more than one place is dereferenced only once.
|
|
146
|
+
* Every later source description referencing it gets its own result carrying
|
|
147
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
148
|
+
* the document was dereferenced; that result is also what gets attached to the
|
|
149
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
150
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
151
|
+
*
|
|
152
152
|
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
153
153
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
154
154
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -183,7 +183,7 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
183
183
|
* @public
|
|
184
184
|
*/
|
|
185
185
|
async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalURI, options, strategyName = 'arazzo-1') {
|
|
186
|
-
const
|
|
186
|
+
const retrievalURI = url.sanitize(url.stripHash(parseResultRetrievalURI));
|
|
187
187
|
const results = [];
|
|
188
188
|
|
|
189
189
|
// get API from dereferenced parse result
|
|
@@ -212,23 +212,24 @@ async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalUR
|
|
|
212
212
|
// recursion state comes from strategy-specific options
|
|
213
213
|
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
214
214
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
visitedUrls.add(baseURI);
|
|
215
|
+
const dereferenced = sharedOpts.sourceDescriptionsDereferenced ?? new Map();
|
|
216
|
+
// current file is an ancestor of everything dereferenced below
|
|
217
|
+
const ancestors = [...(sharedOpts.sourceDescriptionsAncestors ?? []), ...(0, _util2.arazzoDocumentURIs)(retrievalURI, api)];
|
|
219
218
|
if (currentDepth >= maxDepth) {
|
|
220
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${
|
|
219
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${retrievalURI}"`);
|
|
221
220
|
annotation.classes.push('error');
|
|
222
221
|
const parseResult = new _apidomDatamodel.ParseResultElement([annotation]);
|
|
223
222
|
parseResult.classes.push('source-description');
|
|
224
223
|
return [parseResult];
|
|
225
224
|
}
|
|
226
225
|
const ctx = {
|
|
227
|
-
|
|
226
|
+
// base URI for resolving source description URLs honors Arazzo `$self` field
|
|
227
|
+
baseURI: (0, _util2.resolveArazzo$selfField)(retrievalURI, api),
|
|
228
228
|
options,
|
|
229
229
|
strategyName,
|
|
230
230
|
currentDepth,
|
|
231
|
-
|
|
231
|
+
ancestors,
|
|
232
|
+
dereferenced
|
|
232
233
|
};
|
|
233
234
|
|
|
234
235
|
// determine which source descriptions to dereference (array filters by name)
|
|
@@ -239,11 +240,12 @@ async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalUR
|
|
|
239
240
|
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
240
241
|
}) : api.sourceDescriptions;
|
|
241
242
|
|
|
242
|
-
// process sequentially to ensure proper cycle detection with shared
|
|
243
|
+
// process sequentially to ensure proper cycle detection with shared state
|
|
243
244
|
for (const sourceDescription of sourceDescriptions) {
|
|
244
245
|
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
245
|
-
// always attach result (even on failure - contains annotations)
|
|
246
|
-
|
|
246
|
+
// always attach result (even on failure - contains annotations);
|
|
247
|
+
// a shared document attaches the result where it was dereferenced
|
|
248
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult.meta.get('parseResult') ?? sourceDescriptionDereferenceResult);
|
|
247
249
|
results.push(sourceDescriptionDereferenceResult);
|
|
248
250
|
}
|
|
249
251
|
return results;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement, isParseResultElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { isArazzoSpecification1Element, isSourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
3
|
-
import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
|
|
4
|
-
import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
|
|
5
|
-
import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
|
|
6
3
|
import { toValue } from '@speclynx/apidom-core';
|
|
7
4
|
import * as url from "../../../util/url.mjs";
|
|
8
5
|
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
9
6
|
import dereference, { dereferenceApiDOM } from "../../index.mjs";
|
|
7
|
+
import { arazzoDocumentURIs, resolveArazzo$selfField, validateSourceDescriptionAPI } from "./util.mjs";
|
|
10
8
|
/**
|
|
11
9
|
* Dereferences a single source description element.
|
|
12
10
|
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
@@ -36,14 +34,25 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
36
34
|
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
37
35
|
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
38
36
|
|
|
39
|
-
// skip if
|
|
40
|
-
if (ctx.
|
|
37
|
+
// skip if the document is still being dereferenced higher up the chain (cycle detection)
|
|
38
|
+
if (ctx.ancestors.includes(retrievalURI)) {
|
|
41
39
|
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
42
40
|
annotation.classes.push('warning');
|
|
43
41
|
parseResult.push(annotation);
|
|
44
42
|
return parseResult;
|
|
45
43
|
}
|
|
46
|
-
|
|
44
|
+
|
|
45
|
+
// a document reached again through a different path is a shared dependency, not a cycle;
|
|
46
|
+
// point at the result where it was dereferenced instead of dereferencing it again
|
|
47
|
+
const sharedDereferenceResult = ctx.dereferenced.get(retrievalURI);
|
|
48
|
+
if (sharedDereferenceResult !== undefined) {
|
|
49
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been dereferenced. Reusing existing dereference result`);
|
|
50
|
+
annotation.classes.push('info');
|
|
51
|
+
parseResult.push(annotation);
|
|
52
|
+
parseResult.meta.set('parseResult', sharedDereferenceResult);
|
|
53
|
+
validateSourceDescriptionAPI(parseResult, sourceDescription, sharedDereferenceResult.api, retrievalURI);
|
|
54
|
+
return parseResult;
|
|
55
|
+
}
|
|
47
56
|
|
|
48
57
|
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
49
58
|
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
@@ -66,7 +75,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
66
75
|
[ctx.strategyName]: {
|
|
67
76
|
sourceDescriptions: true,
|
|
68
77
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
69
|
-
|
|
78
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
79
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
}
|
|
@@ -85,7 +95,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
85
95
|
[ctx.strategyName]: {
|
|
86
96
|
sourceDescriptions: true,
|
|
87
97
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
88
|
-
|
|
98
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
99
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
102
|
}
|
|
@@ -105,32 +116,14 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
105
116
|
return parseResult;
|
|
106
117
|
}
|
|
107
118
|
|
|
108
|
-
//
|
|
119
|
+
// register dereferenced document for later references to it
|
|
109
120
|
const {
|
|
110
121
|
api: sourceDescriptionAPI
|
|
111
122
|
} = parseResult;
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
if (!isOpenApi && !isArazzo) {
|
|
115
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
116
|
-
annotation.classes.push('warning');
|
|
117
|
-
parseResult.push(annotation);
|
|
118
|
-
return parseResult;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// validate declared type matches actual dereferenced type
|
|
122
|
-
const declaredType = toValue(sourceDescription.type);
|
|
123
|
-
if (typeof declaredType === 'string') {
|
|
124
|
-
if (declaredType === 'openapi' && !isOpenApi) {
|
|
125
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but dereferenced as Arazzo document`);
|
|
126
|
-
annotation.classes.push('warning');
|
|
127
|
-
parseResult.push(annotation);
|
|
128
|
-
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
129
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but dereferenced as OpenAPI document`);
|
|
130
|
-
annotation.classes.push('warning');
|
|
131
|
-
parseResult.push(annotation);
|
|
132
|
-
}
|
|
123
|
+
for (const documentURI of arazzoDocumentURIs(retrievalURI, sourceDescriptionAPI)) {
|
|
124
|
+
ctx.dereferenced.set(documentURI, parseResult);
|
|
133
125
|
}
|
|
126
|
+
validateSourceDescriptionAPI(parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI);
|
|
134
127
|
return parseResult;
|
|
135
128
|
}
|
|
136
129
|
|
|
@@ -142,6 +135,13 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
142
135
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
143
136
|
* contains annotations explaining what went wrong.
|
|
144
137
|
*
|
|
138
|
+
* A document referenced from more than one place is dereferenced only once.
|
|
139
|
+
* Every later source description referencing it gets its own result carrying
|
|
140
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
141
|
+
* the document was dereferenced; that result is also what gets attached to the
|
|
142
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
143
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
144
|
+
*
|
|
145
145
|
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
146
146
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
147
147
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -176,7 +176,7 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
176
176
|
* @public
|
|
177
177
|
*/
|
|
178
178
|
export async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalURI, options, strategyName = 'arazzo-1') {
|
|
179
|
-
const
|
|
179
|
+
const retrievalURI = url.sanitize(url.stripHash(parseResultRetrievalURI));
|
|
180
180
|
const results = [];
|
|
181
181
|
|
|
182
182
|
// get API from dereferenced parse result
|
|
@@ -205,23 +205,24 @@ export async function dereferenceSourceDescriptions(parseResult, parseResultRetr
|
|
|
205
205
|
// recursion state comes from strategy-specific options
|
|
206
206
|
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
207
207
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
visitedUrls.add(baseURI);
|
|
208
|
+
const dereferenced = sharedOpts.sourceDescriptionsDereferenced ?? new Map();
|
|
209
|
+
// current file is an ancestor of everything dereferenced below
|
|
210
|
+
const ancestors = [...(sharedOpts.sourceDescriptionsAncestors ?? []), ...arazzoDocumentURIs(retrievalURI, api)];
|
|
212
211
|
if (currentDepth >= maxDepth) {
|
|
213
|
-
const annotation = new AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${
|
|
212
|
+
const annotation = new AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${retrievalURI}"`);
|
|
214
213
|
annotation.classes.push('error');
|
|
215
214
|
const parseResult = new ParseResultElement([annotation]);
|
|
216
215
|
parseResult.classes.push('source-description');
|
|
217
216
|
return [parseResult];
|
|
218
217
|
}
|
|
219
218
|
const ctx = {
|
|
220
|
-
|
|
219
|
+
// base URI for resolving source description URLs honors Arazzo `$self` field
|
|
220
|
+
baseURI: resolveArazzo$selfField(retrievalURI, api),
|
|
221
221
|
options,
|
|
222
222
|
strategyName,
|
|
223
223
|
currentDepth,
|
|
224
|
-
|
|
224
|
+
ancestors,
|
|
225
|
+
dereferenced
|
|
225
226
|
};
|
|
226
227
|
|
|
227
228
|
// determine which source descriptions to dereference (array filters by name)
|
|
@@ -232,11 +233,12 @@ export async function dereferenceSourceDescriptions(parseResult, parseResultRetr
|
|
|
232
233
|
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
233
234
|
}) : api.sourceDescriptions;
|
|
234
235
|
|
|
235
|
-
// process sequentially to ensure proper cycle detection with shared
|
|
236
|
+
// process sequentially to ensure proper cycle detection with shared state
|
|
236
237
|
for (const sourceDescription of sourceDescriptions) {
|
|
237
238
|
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
238
|
-
// always attach result (even on failure - contains annotations)
|
|
239
|
-
|
|
239
|
+
// always attach result (even on failure - contains annotations);
|
|
240
|
+
// a shared document attaches the result where it was dereferenced
|
|
241
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult.meta.get('parseResult') ?? sourceDescriptionDereferenceResult);
|
|
240
242
|
results.push(sourceDescriptionDereferenceResult);
|
|
241
243
|
}
|
|
242
244
|
return results;
|