@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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
|
-
exports.refractToJSONSchemaElement = exports.maybeRefractToJSONSchemaElement = void 0;
|
|
7
|
+
exports.resolveArazzo$selfField = exports.refractToJSONSchemaElement = exports.maybeRefractToJSONSchemaElement = exports.identifiedBy$self = exports.arazzoDocumentURIs = void 0;
|
|
7
8
|
Object.defineProperty(exports, "resolveSchema$idField", {
|
|
8
9
|
enumerable: true,
|
|
9
10
|
get: function () {
|
|
@@ -16,12 +17,96 @@ Object.defineProperty(exports, "resolveSchema$refField", {
|
|
|
16
17
|
return _util.resolveSchema$refField;
|
|
17
18
|
}
|
|
18
19
|
});
|
|
20
|
+
exports.validateSourceDescriptionAPI = void 0;
|
|
19
21
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
22
|
+
var _apidomCore = require("@speclynx/apidom-core");
|
|
20
23
|
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
24
|
+
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
25
|
+
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
26
|
+
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
27
|
+
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
21
28
|
var _util = require("../openapi-3-1/util.cjs");
|
|
29
|
+
/**
|
|
30
|
+
* Computes the base URI of an Arazzo document per Arazzo 1.1.0 rules.
|
|
31
|
+
*
|
|
32
|
+
* When the Arazzo Object carries a `$self` field, it establishes the document's
|
|
33
|
+
* canonical identity and the base URI for resolving relative references. `$self`
|
|
34
|
+
* may itself be relative, in which case it's resolved against the retrieval URI.
|
|
35
|
+
* Any fragment is stripped, as `$self` MUST NOT contain one. When `$self` is
|
|
36
|
+
* absent (or `element` is not an Arazzo document), the retrieval URI is the base URI.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
const resolveArazzo$selfField = (retrievalURI, element) => {
|
|
41
|
+
if (!(0, _apidomNsArazzo.isArazzoSpecification1Element)(element)) {
|
|
42
|
+
return retrievalURI;
|
|
43
|
+
}
|
|
44
|
+
const $self = (0, _apidomCore.toValue)(element.$self);
|
|
45
|
+
if (typeof $self !== 'string' || $self === '') {
|
|
46
|
+
return retrievalURI;
|
|
47
|
+
}
|
|
48
|
+
return url.resolve(retrievalURI, url.sanitize(url.stripHash($self)));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* URIs an Arazzo document is known by: its retrieval URI and, when the
|
|
53
|
+
* document carries a `$self` field resolving elsewhere, its identity.
|
|
54
|
+
*/
|
|
55
|
+
exports.resolveArazzo$selfField = resolveArazzo$selfField;
|
|
56
|
+
const arazzoDocumentURIs = (retrievalURI, element) => {
|
|
57
|
+
const identity = resolveArazzo$selfField(retrievalURI, element);
|
|
58
|
+
return identity === retrievalURI ? [retrievalURI] : [retrievalURI, identity];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Annotates a source description result when the document is not an OpenAPI
|
|
63
|
+
* or Arazzo document, or does not match the type the source description declares.
|
|
64
|
+
*/
|
|
65
|
+
exports.arazzoDocumentURIs = arazzoDocumentURIs;
|
|
66
|
+
const validateSourceDescriptionAPI = (parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI) => {
|
|
67
|
+
// only allow OpenAPI and Arazzo as source descriptions
|
|
68
|
+
const isOpenApi = (0, _apidomNsOpenapi.isSwaggerElement)(sourceDescriptionAPI) || (0, _apidomNsOpenapi2.isOpenApi3_0Element)(sourceDescriptionAPI) || (0, _apidomNsOpenapi3.isOpenApi3_1Element)(sourceDescriptionAPI);
|
|
69
|
+
const isArazzo = (0, _apidomNsArazzo.isArazzoSpecification1Element)(sourceDescriptionAPI);
|
|
70
|
+
if (!isOpenApi && !isArazzo) {
|
|
71
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
72
|
+
annotation.classes.push('warning');
|
|
73
|
+
parseResult.push(annotation);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// validate declared type matches actual type
|
|
78
|
+
const declaredType = (0, _apidomCore.toValue)(sourceDescription.type);
|
|
79
|
+
if (typeof declaredType === 'string') {
|
|
80
|
+
if (declaredType === 'openapi' && !isOpenApi) {
|
|
81
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but is an Arazzo document`);
|
|
82
|
+
annotation.classes.push('warning');
|
|
83
|
+
parseResult.push(annotation);
|
|
84
|
+
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
85
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but is an OpenAPI document`);
|
|
86
|
+
annotation.classes.push('warning');
|
|
87
|
+
parseResult.push(annotation);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Predicate matching a Reference by the identity (`$self`) of the Arazzo
|
|
94
|
+
* document it holds, rather than by its retrieval URI. Implements identity-based
|
|
95
|
+
* referencing from Arazzo 1.1.0: a URI matching a loaded document's `$self`
|
|
96
|
+
* refers to that document, regardless of where it was retrieved from.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
exports.validateSourceDescriptionAPI = validateSourceDescriptionAPI;
|
|
101
|
+
const identifiedBy$self = uri => reference => {
|
|
102
|
+
const result = reference.value?.result;
|
|
103
|
+
return (0, _apidomNsArazzo.isArazzoSpecification1Element)(result) && resolveArazzo$selfField(reference.uri, result) === uri;
|
|
104
|
+
};
|
|
105
|
+
|
|
22
106
|
/**
|
|
23
107
|
* Cached version of JSONSchemaElement.refract.
|
|
24
108
|
*/
|
|
109
|
+
exports.identifiedBy$self = identifiedBy$self;
|
|
25
110
|
const refractToJSONSchemaElement = element => {
|
|
26
111
|
if (refractToJSONSchemaElement.cache.has(element)) {
|
|
27
112
|
return refractToJSONSchemaElement.cache.get(element);
|
|
@@ -1,6 +1,85 @@
|
|
|
1
|
-
import { isPrimitiveElement } from '@speclynx/apidom-datamodel';
|
|
2
|
-
import {
|
|
1
|
+
import { AnnotationElement, isPrimitiveElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { toValue } from '@speclynx/apidom-core';
|
|
3
|
+
import { refractJSONSchema, isArazzoSpecification1Element } from '@speclynx/apidom-ns-arazzo-1';
|
|
4
|
+
import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
|
|
5
|
+
import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
|
|
6
|
+
import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
|
|
7
|
+
import * as url from "../../../util/url.mjs";
|
|
3
8
|
export { resolveSchema$refField, resolveSchema$idField } from "../openapi-3-1/util.mjs";
|
|
9
|
+
/**
|
|
10
|
+
* Computes the base URI of an Arazzo document per Arazzo 1.1.0 rules.
|
|
11
|
+
*
|
|
12
|
+
* When the Arazzo Object carries a `$self` field, it establishes the document's
|
|
13
|
+
* canonical identity and the base URI for resolving relative references. `$self`
|
|
14
|
+
* may itself be relative, in which case it's resolved against the retrieval URI.
|
|
15
|
+
* Any fragment is stripped, as `$self` MUST NOT contain one. When `$self` is
|
|
16
|
+
* absent (or `element` is not an Arazzo document), the retrieval URI is the base URI.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export const resolveArazzo$selfField = (retrievalURI, element) => {
|
|
21
|
+
if (!isArazzoSpecification1Element(element)) {
|
|
22
|
+
return retrievalURI;
|
|
23
|
+
}
|
|
24
|
+
const $self = toValue(element.$self);
|
|
25
|
+
if (typeof $self !== 'string' || $self === '') {
|
|
26
|
+
return retrievalURI;
|
|
27
|
+
}
|
|
28
|
+
return url.resolve(retrievalURI, url.sanitize(url.stripHash($self)));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* URIs an Arazzo document is known by: its retrieval URI and, when the
|
|
33
|
+
* document carries a `$self` field resolving elsewhere, its identity.
|
|
34
|
+
*/
|
|
35
|
+
export const arazzoDocumentURIs = (retrievalURI, element) => {
|
|
36
|
+
const identity = resolveArazzo$selfField(retrievalURI, element);
|
|
37
|
+
return identity === retrievalURI ? [retrievalURI] : [retrievalURI, identity];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Annotates a source description result when the document is not an OpenAPI
|
|
42
|
+
* or Arazzo document, or does not match the type the source description declares.
|
|
43
|
+
*/
|
|
44
|
+
export const validateSourceDescriptionAPI = (parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI) => {
|
|
45
|
+
// only allow OpenAPI and Arazzo as source descriptions
|
|
46
|
+
const isOpenApi = isSwaggerElement(sourceDescriptionAPI) || isOpenApi3_0Element(sourceDescriptionAPI) || isOpenApi3_1Element(sourceDescriptionAPI);
|
|
47
|
+
const isArazzo = isArazzoSpecification1Element(sourceDescriptionAPI);
|
|
48
|
+
if (!isOpenApi && !isArazzo) {
|
|
49
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
50
|
+
annotation.classes.push('warning');
|
|
51
|
+
parseResult.push(annotation);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// validate declared type matches actual type
|
|
56
|
+
const declaredType = toValue(sourceDescription.type);
|
|
57
|
+
if (typeof declaredType === 'string') {
|
|
58
|
+
if (declaredType === 'openapi' && !isOpenApi) {
|
|
59
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but is an Arazzo document`);
|
|
60
|
+
annotation.classes.push('warning');
|
|
61
|
+
parseResult.push(annotation);
|
|
62
|
+
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
63
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but is an OpenAPI document`);
|
|
64
|
+
annotation.classes.push('warning');
|
|
65
|
+
parseResult.push(annotation);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Predicate matching a Reference by the identity (`$self`) of the Arazzo
|
|
72
|
+
* document it holds, rather than by its retrieval URI. Implements identity-based
|
|
73
|
+
* referencing from Arazzo 1.1.0: a URI matching a loaded document's `$self`
|
|
74
|
+
* refers to that document, regardless of where it was retrieved from.
|
|
75
|
+
*
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export const identifiedBy$self = uri => reference => {
|
|
79
|
+
const result = reference.value?.result;
|
|
80
|
+
return isArazzoSpecification1Element(result) && resolveArazzo$selfField(reference.uri, result) === uri;
|
|
81
|
+
};
|
|
82
|
+
|
|
4
83
|
/**
|
|
5
84
|
* Cached version of JSONSchemaElement.refract.
|
|
6
85
|
*/
|
|
@@ -37,6 +37,13 @@ var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../err
|
|
|
37
37
|
class Arazzo1DereferenceVisitor {
|
|
38
38
|
indirections;
|
|
39
39
|
reference;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Base URI of the current document for resolving relative references.
|
|
43
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back
|
|
44
|
+
* to the retrieval URI (`this.reference.uri`).
|
|
45
|
+
*/
|
|
46
|
+
baseURI;
|
|
40
47
|
options;
|
|
41
48
|
visited;
|
|
42
49
|
|
|
@@ -55,6 +62,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
55
62
|
}) {
|
|
56
63
|
this.indirections = indirections;
|
|
57
64
|
this.reference = reference;
|
|
65
|
+
this.baseURI = (0, _util2.resolveArazzo$selfField)(reference.uri, reference.value?.result);
|
|
58
66
|
this.options = options;
|
|
59
67
|
this.ancestors = new _util3.AncestorLineage(...ancestors);
|
|
60
68
|
this.visited = visited;
|
|
@@ -66,7 +74,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
66
74
|
return [ancestorsLineage, directAncestors];
|
|
67
75
|
}
|
|
68
76
|
toBaseURI(uri) {
|
|
69
|
-
return url.resolve(this.
|
|
77
|
+
return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
|
|
70
78
|
}
|
|
71
79
|
async toReference(uri) {
|
|
72
80
|
// detect maximum depth of resolution
|
|
@@ -85,6 +93,12 @@ class Arazzo1DereferenceVisitor {
|
|
|
85
93
|
if (refSet.has(baseURI)) {
|
|
86
94
|
return refSet.find((0, _ramda.propEq)(baseURI, 'uri'));
|
|
87
95
|
}
|
|
96
|
+
|
|
97
|
+
// identity-based referencing: URI matches `$self` of an already processed Arazzo document
|
|
98
|
+
const referenceBy$self = refSet.find((0, _util2.identifiedBy$self)(baseURI));
|
|
99
|
+
if (referenceBy$self !== undefined) {
|
|
100
|
+
return referenceBy$self;
|
|
101
|
+
}
|
|
88
102
|
const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
|
|
89
103
|
...this.options,
|
|
90
104
|
parse: {
|
|
@@ -288,19 +302,19 @@ class Arazzo1DereferenceVisitor {
|
|
|
288
302
|
}
|
|
289
303
|
const indirectionsSize = this.indirections.length;
|
|
290
304
|
try {
|
|
291
|
-
// compute baseURI using rules around $id and $ref keywords
|
|
305
|
+
// compute baseURI using rules around $self, $id and $ref keywords
|
|
292
306
|
let reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
293
307
|
let {
|
|
294
308
|
uri: retrievalURI
|
|
295
309
|
} = reference;
|
|
296
|
-
const $refBaseURI = (0, _util.resolveSchema$refField)(
|
|
310
|
+
const $refBaseURI = (0, _util.resolveSchema$refField)(this.baseURI, referencingElement);
|
|
297
311
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
298
312
|
const file = new _File.default({
|
|
299
313
|
uri: $refBaseURIStrippedHash
|
|
300
314
|
});
|
|
301
315
|
const isUnknownURI = (0, _ramda.none)(r => r.canRead(file), this.options.resolve.resolvers);
|
|
302
316
|
const isURL = !isUnknownURI;
|
|
303
|
-
let isInternalReference =
|
|
317
|
+
let isInternalReference = this.baseURI === $refBaseURI;
|
|
304
318
|
let isExternalReference = !isInternalReference;
|
|
305
319
|
|
|
306
320
|
// determining reference, proper evaluation and selection mechanism
|
|
@@ -327,7 +341,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
327
341
|
} else {
|
|
328
342
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
329
343
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
330
|
-
isInternalReference =
|
|
344
|
+
isInternalReference = this.baseURI === retrievalURI;
|
|
331
345
|
isExternalReference = !isInternalReference;
|
|
332
346
|
|
|
333
347
|
// ignore resolving internal Schema Objects
|
|
@@ -354,7 +368,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
354
368
|
if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) {
|
|
355
369
|
if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) {
|
|
356
370
|
// we're dealing with JSON Schema $anchor here
|
|
357
|
-
isInternalReference =
|
|
371
|
+
isInternalReference = this.baseURI === retrievalURI;
|
|
358
372
|
isExternalReference = !isInternalReference;
|
|
359
373
|
|
|
360
374
|
// ignore resolving internal Schema Objects
|
|
@@ -375,7 +389,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
375
389
|
} else {
|
|
376
390
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
377
391
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
378
|
-
isInternalReference =
|
|
392
|
+
isInternalReference = this.baseURI === retrievalURI;
|
|
379
393
|
isExternalReference = !isInternalReference;
|
|
380
394
|
|
|
381
395
|
// ignore resolving internal Schema Objects
|
|
@@ -9,7 +9,7 @@ import { parse as parseRuntimeExpression } from '@swaggerexpert/arazzo-runtime-e
|
|
|
9
9
|
import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "./selectors/$anchor.mjs";
|
|
10
10
|
import { evaluate as uriEvaluate } from "./selectors/uri.mjs";
|
|
11
11
|
import { resolveSchema$refField } from "../openapi-3-1/util.mjs";
|
|
12
|
-
import { maybeRefractToJSONSchemaElement } from "./util.mjs";
|
|
12
|
+
import { maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "./util.mjs";
|
|
13
13
|
import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
|
|
14
14
|
import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
|
|
15
15
|
import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
|
|
@@ -28,6 +28,13 @@ import EvaluationJsonSchemaUriError from "../../../errors/EvaluationJsonSchemaUr
|
|
|
28
28
|
class Arazzo1DereferenceVisitor {
|
|
29
29
|
indirections;
|
|
30
30
|
reference;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base URI of the current document for resolving relative references.
|
|
34
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back
|
|
35
|
+
* to the retrieval URI (`this.reference.uri`).
|
|
36
|
+
*/
|
|
37
|
+
baseURI;
|
|
31
38
|
options;
|
|
32
39
|
visited;
|
|
33
40
|
|
|
@@ -46,6 +53,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
46
53
|
}) {
|
|
47
54
|
this.indirections = indirections;
|
|
48
55
|
this.reference = reference;
|
|
56
|
+
this.baseURI = resolveArazzo$selfField(reference.uri, reference.value?.result);
|
|
49
57
|
this.options = options;
|
|
50
58
|
this.ancestors = new AncestorLineage(...ancestors);
|
|
51
59
|
this.visited = visited;
|
|
@@ -57,7 +65,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
57
65
|
return [ancestorsLineage, directAncestors];
|
|
58
66
|
}
|
|
59
67
|
toBaseURI(uri) {
|
|
60
|
-
return url.resolve(this.
|
|
68
|
+
return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
|
|
61
69
|
}
|
|
62
70
|
async toReference(uri) {
|
|
63
71
|
// detect maximum depth of resolution
|
|
@@ -76,6 +84,12 @@ class Arazzo1DereferenceVisitor {
|
|
|
76
84
|
if (refSet.has(baseURI)) {
|
|
77
85
|
return refSet.find(propEq(baseURI, 'uri'));
|
|
78
86
|
}
|
|
87
|
+
|
|
88
|
+
// identity-based referencing: URI matches `$self` of an already processed Arazzo document
|
|
89
|
+
const referenceBy$self = refSet.find(identifiedBy$self(baseURI));
|
|
90
|
+
if (referenceBy$self !== undefined) {
|
|
91
|
+
return referenceBy$self;
|
|
92
|
+
}
|
|
79
93
|
const parseResult = await parse(url.unsanitize(baseURI), {
|
|
80
94
|
...this.options,
|
|
81
95
|
parse: {
|
|
@@ -279,19 +293,19 @@ class Arazzo1DereferenceVisitor {
|
|
|
279
293
|
}
|
|
280
294
|
const indirectionsSize = this.indirections.length;
|
|
281
295
|
try {
|
|
282
|
-
// compute baseURI using rules around $id and $ref keywords
|
|
296
|
+
// compute baseURI using rules around $self, $id and $ref keywords
|
|
283
297
|
let reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
284
298
|
let {
|
|
285
299
|
uri: retrievalURI
|
|
286
300
|
} = reference;
|
|
287
|
-
const $refBaseURI = resolveSchema$refField(
|
|
301
|
+
const $refBaseURI = resolveSchema$refField(this.baseURI, referencingElement);
|
|
288
302
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
289
303
|
const file = new File({
|
|
290
304
|
uri: $refBaseURIStrippedHash
|
|
291
305
|
});
|
|
292
306
|
const isUnknownURI = none(r => r.canRead(file), this.options.resolve.resolvers);
|
|
293
307
|
const isURL = !isUnknownURI;
|
|
294
|
-
let isInternalReference =
|
|
308
|
+
let isInternalReference = this.baseURI === $refBaseURI;
|
|
295
309
|
let isExternalReference = !isInternalReference;
|
|
296
310
|
|
|
297
311
|
// determining reference, proper evaluation and selection mechanism
|
|
@@ -318,7 +332,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
318
332
|
} else {
|
|
319
333
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
320
334
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
321
|
-
isInternalReference =
|
|
335
|
+
isInternalReference = this.baseURI === retrievalURI;
|
|
322
336
|
isExternalReference = !isInternalReference;
|
|
323
337
|
|
|
324
338
|
// ignore resolving internal Schema Objects
|
|
@@ -345,7 +359,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
345
359
|
if (isURL && error instanceof EvaluationJsonSchemaUriError) {
|
|
346
360
|
if (isAnchor(uriToAnchor($refBaseURI))) {
|
|
347
361
|
// we're dealing with JSON Schema $anchor here
|
|
348
|
-
isInternalReference =
|
|
362
|
+
isInternalReference = this.baseURI === retrievalURI;
|
|
349
363
|
isExternalReference = !isInternalReference;
|
|
350
364
|
|
|
351
365
|
// ignore resolving internal Schema Objects
|
|
@@ -366,7 +380,7 @@ class Arazzo1DereferenceVisitor {
|
|
|
366
380
|
} else {
|
|
367
381
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
368
382
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
369
|
-
isInternalReference =
|
|
383
|
+
isInternalReference = this.baseURI === retrievalURI;
|
|
370
384
|
isExternalReference = !isInternalReference;
|
|
371
385
|
|
|
372
386
|
// ignore resolving internal Schema Objects
|
|
@@ -8,14 +8,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
8
8
|
exports.parseSourceDescriptions = parseSourceDescriptions;
|
|
9
9
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
10
10
|
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
11
|
-
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
12
|
-
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
13
|
-
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
14
11
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
15
12
|
var _File = _interopRequireDefault(require("../../../File.cjs"));
|
|
16
13
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
17
14
|
var _util = require("../../../options/util.cjs");
|
|
18
15
|
var _index = _interopRequireDefault(require("../../index.cjs"));
|
|
16
|
+
var _util2 = require("../../../dereference/strategies/arazzo-1/util.cjs");
|
|
19
17
|
// shared key for recursion state (works across JSON/YAML parsers)
|
|
20
18
|
const ARAZZO_RECURSION_KEY = 'arazzo-1';
|
|
21
19
|
/**
|
|
@@ -47,14 +45,25 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
47
45
|
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
48
46
|
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
49
47
|
|
|
50
|
-
// skip if
|
|
51
|
-
if (ctx.
|
|
48
|
+
// skip if the document is still being parsed higher up the chain (cycle detection)
|
|
49
|
+
if (ctx.ancestors.includes(retrievalURI)) {
|
|
52
50
|
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
53
51
|
annotation.classes.push('warning');
|
|
54
52
|
parseResult.push(annotation);
|
|
55
53
|
return parseResult;
|
|
56
54
|
}
|
|
57
|
-
|
|
55
|
+
|
|
56
|
+
// a document reached again through a different path is a shared dependency, not a cycle;
|
|
57
|
+
// point at the result where it was parsed instead of parsing it again
|
|
58
|
+
const sharedParseResult = ctx.parsed.get(retrievalURI);
|
|
59
|
+
if (sharedParseResult !== undefined) {
|
|
60
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been parsed. Reusing existing parse result`);
|
|
61
|
+
annotation.classes.push('info');
|
|
62
|
+
parseResult.push(annotation);
|
|
63
|
+
parseResult.meta.set('parseResult', sharedParseResult);
|
|
64
|
+
(0, _util2.validateSourceDescriptionAPI)(parseResult, sourceDescription, sharedParseResult.api, retrievalURI);
|
|
65
|
+
return parseResult;
|
|
66
|
+
}
|
|
58
67
|
try {
|
|
59
68
|
const sourceDescriptionParseResult = await (0, _index.default)(retrievalURI, (0, _util.merge)(ctx.options, {
|
|
60
69
|
parse: {
|
|
@@ -66,7 +75,8 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
66
75
|
sourceDescriptions: true,
|
|
67
76
|
[ARAZZO_RECURSION_KEY]: {
|
|
68
77
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
69
|
-
|
|
78
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
79
|
+
sourceDescriptionsParsed: ctx.parsed
|
|
70
80
|
}
|
|
71
81
|
}
|
|
72
82
|
}
|
|
@@ -84,32 +94,14 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
84
94
|
return parseResult;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
//
|
|
97
|
+
// register parsed document for later references to it
|
|
88
98
|
const {
|
|
89
99
|
api: sourceDescriptionAPI
|
|
90
100
|
} = parseResult;
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
if (!isOpenApi && !isArazzo) {
|
|
94
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
95
|
-
annotation.classes.push('warning');
|
|
96
|
-
parseResult.push(annotation);
|
|
97
|
-
return parseResult;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// validate declared type matches actual parsed type
|
|
101
|
-
const declaredType = (0, _apidomCore.toValue)(sourceDescription.type);
|
|
102
|
-
if (typeof declaredType === 'string') {
|
|
103
|
-
if (declaredType === 'openapi' && !isOpenApi) {
|
|
104
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but parsed as Arazzo document`);
|
|
105
|
-
annotation.classes.push('warning');
|
|
106
|
-
parseResult.push(annotation);
|
|
107
|
-
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
108
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but parsed as OpenAPI document`);
|
|
109
|
-
annotation.classes.push('warning');
|
|
110
|
-
parseResult.push(annotation);
|
|
111
|
-
}
|
|
101
|
+
for (const documentURI of (0, _util2.arazzoDocumentURIs)(retrievalURI, sourceDescriptionAPI)) {
|
|
102
|
+
ctx.parsed.set(documentURI, parseResult);
|
|
112
103
|
}
|
|
104
|
+
(0, _util2.validateSourceDescriptionAPI)(parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI);
|
|
113
105
|
return parseResult;
|
|
114
106
|
}
|
|
115
107
|
|
|
@@ -121,6 +113,13 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
121
113
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
122
114
|
* contains annotations explaining what went wrong.
|
|
123
115
|
*
|
|
116
|
+
* A document referenced from more than one place is parsed only once.
|
|
117
|
+
* Every later source description referencing it gets its own result carrying
|
|
118
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
119
|
+
* the document was parsed; that result is also what gets attached to the
|
|
120
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
121
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
122
|
+
*
|
|
124
123
|
* @param parseResult - ParseResult containing an Arazzo specification
|
|
125
124
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
126
125
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -186,10 +185,9 @@ async function parseSourceDescriptions(parseResult, parseResultRetrievalURI, opt
|
|
|
186
185
|
// recursion state comes from shared key (works across JSON/YAML)
|
|
187
186
|
const sharedOpts = options?.parse?.parserOpts?.[ARAZZO_RECURSION_KEY] ?? {};
|
|
188
187
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
visitedUrls.add(file.uri);
|
|
188
|
+
const parsed = sharedOpts.sourceDescriptionsParsed ?? new Map();
|
|
189
|
+
// current file is an ancestor of everything parsed below
|
|
190
|
+
const ancestors = [...(sharedOpts.sourceDescriptionsAncestors ?? []), ...(0, _util2.arazzoDocumentURIs)(file.uri, api)];
|
|
193
191
|
if (currentDepth >= maxDepth) {
|
|
194
192
|
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum parse depth of ${maxDepth} has been exceeded by file "${file.uri}"`);
|
|
195
193
|
annotation.classes.push('error');
|
|
@@ -198,10 +196,12 @@ async function parseSourceDescriptions(parseResult, parseResultRetrievalURI, opt
|
|
|
198
196
|
return [parseResult];
|
|
199
197
|
}
|
|
200
198
|
const ctx = {
|
|
201
|
-
|
|
199
|
+
// base URI for resolving source description URLs honors Arazzo `$self` field
|
|
200
|
+
baseURI: (0, _util2.resolveArazzo$selfField)(file.uri, api),
|
|
202
201
|
options,
|
|
203
202
|
currentDepth,
|
|
204
|
-
|
|
203
|
+
ancestors,
|
|
204
|
+
parsed
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
// determine which source descriptions to parse (array filters by name)
|
|
@@ -212,11 +212,12 @@ async function parseSourceDescriptions(parseResult, parseResultRetrievalURI, opt
|
|
|
212
212
|
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
213
213
|
}) : api.sourceDescriptions;
|
|
214
214
|
|
|
215
|
-
// process sequentially to ensure proper cycle detection with shared
|
|
215
|
+
// process sequentially to ensure proper cycle detection with shared state
|
|
216
216
|
for (const sourceDescription of sourceDescriptions) {
|
|
217
217
|
const sourceDescriptionParseResult = await parseSourceDescription(sourceDescription, ctx);
|
|
218
|
-
// always attach result (even on failure - contains annotations)
|
|
219
|
-
|
|
218
|
+
// always attach result (even on failure - contains annotations);
|
|
219
|
+
// a shared document attaches the result where it was parsed
|
|
220
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionParseResult.meta.get('parseResult') ?? sourceDescriptionParseResult);
|
|
220
221
|
results.push(sourceDescriptionParseResult);
|
|
221
222
|
}
|
|
222
223
|
return results;
|