@speclynx/apidom-reference 5.2.4 → 5.2.6
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 +19 -0
- package/README.md +37 -14
- package/dist/apidom-reference.browser.js +1234 -596
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +29 -29
- 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 +20 -17
- package/src/dereference/strategies/arazzo-1/index.mjs +14 -11
- 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/source-descriptions.cjs +65 -42
- package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +64 -42
- 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 +97 -35
- package/src/dereference/strategies/arazzo-1/visitor.mjs +101 -39
- 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
|
@@ -4,39 +4,100 @@ var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopReq
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.resolveSchema$refField = exports.resolveSchema$idField = exports.refractToSchemaElement = exports.maybeRefractToSchemaElement = void 0;
|
|
7
|
+
exports.schema$idsOf = exports.resolveSchemaBaseURI = exports.resolveSchema$refField = exports.resolveSchema$ids = exports.resolveSchema$idField = exports.refractToSchemaElement = exports.maybeRefractToSchemaElement = exports.isJSONSchemaElementWith$id = exports.detachedRootSchema$ids = exports.collectSchema$ids = void 0;
|
|
8
8
|
var _ramda = require("ramda");
|
|
9
|
+
var _ramdaAdjunct = require("ramda-adjunct");
|
|
9
10
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
10
11
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
12
|
+
var _apidomNsJsonSchema = require("@speclynx/apidom-ns-json-schema-2020-12");
|
|
11
13
|
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-3-1");
|
|
12
14
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
13
15
|
/**
|
|
16
|
+
* A schema located within a document, together with the `$id`s of the JSON Schema
|
|
17
|
+
* elements enclosing it (outermost first; the located schema's own `$id` excluded).
|
|
14
18
|
* @public
|
|
15
19
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether the element is a JSON Schema element declaring a `$id`.
|
|
23
|
+
*/
|
|
24
|
+
const isJSONSchemaElementWith$id = element => (0, _apidomNsJsonSchema.isJSONSchemaElement)(element) && (0, _apidomDatamodel.isStringElement)(element.$id);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `$id`s declared by the JSON Schema elements among `nodes`, in order.
|
|
28
|
+
*/
|
|
29
|
+
exports.isJSONSchemaElementWith$id = isJSONSchemaElementWith$id;
|
|
30
|
+
const schema$idsOf = nodes => nodes.filter(isJSONSchemaElementWith$id).map(node => (0, _apidomCore.toValue)(node.$id));
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `$id`s of the JSON Schema elements on `path` (outermost first, the node itself
|
|
34
|
+
* included). The `$id`s are read from the elements themselves rather than from
|
|
35
|
+
* refraction-time metadata, so a `$id` assigned after refraction (e.g. by the
|
|
36
|
+
* bundler) is honored.
|
|
37
|
+
*/
|
|
38
|
+
exports.schema$idsOf = schema$idsOf;
|
|
39
|
+
const collectSchema$ids = path => schema$idsOf([...path.getAncestorNodes().reverse(), path.node]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `$id`s of the schemas enclosing a traversal root that is a fragment detached
|
|
43
|
+
* from its document, read from the `ancestorsSchemaIdentifiers` meta the JSON
|
|
44
|
+
* Schema refractors record. The meta also carries the root's own `$id`, which
|
|
45
|
+
* the ancestor walk contributes itself, so it is dropped. A detached fragment
|
|
46
|
+
* has no live ancestors, so the meta is the only carrier of its lexical
|
|
47
|
+
* context; this is the one reader of it the refractors must keep serving.
|
|
48
|
+
*/
|
|
49
|
+
exports.collectSchema$ids = collectSchema$ids;
|
|
50
|
+
const detachedRootSchema$ids = root => {
|
|
51
|
+
const meta = root?.meta.get('ancestorsSchemaIdentifiers');
|
|
52
|
+
const $ids = Array.isArray(meta) ? meta.filter(_ramdaAdjunct.isString) : [];
|
|
53
|
+
const own$id = isJSONSchemaElementWith$id(root) ? (0, _apidomCore.toValue)(root.$id) : undefined;
|
|
54
|
+
return $ids.length > 0 && $ids.at(-1) === own$id ? $ids.slice(0, -1) : $ids;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolves a chain of `$id`s (outermost first) against `baseURI`, each `$id`
|
|
59
|
+
* refining the base the next one resolves against.
|
|
60
|
+
*/
|
|
61
|
+
exports.detachedRootSchema$ids = detachedRootSchema$ids;
|
|
62
|
+
const resolveSchema$ids = (baseURI, $ids) => (0, _ramda.reduce)((acc, $id) => url.resolve(acc, url.sanitize(url.stripHash($id))), baseURI, $ids);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the base URI of the schema at `path`: `baseURI` (the base URI in
|
|
66
|
+
* effect at the traversal root) refined by each `$id` on the schema's ancestor
|
|
67
|
+
* chain in turn, the schema's own `$id` included.
|
|
68
|
+
*/
|
|
69
|
+
exports.resolveSchema$ids = resolveSchema$ids;
|
|
70
|
+
const resolveSchemaBaseURI = (baseURI, path) => resolveSchema$ids(baseURI, collectSchema$ids(path));
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolves the `$ref` of the schema against its base URI (see
|
|
74
|
+
* `resolveSchemaBaseURI`), or returns `undefined` when the schema has no string `$ref`.
|
|
75
|
+
*
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
exports.resolveSchemaBaseURI = resolveSchemaBaseURI;
|
|
79
|
+
const resolveSchema$refField = (schemaBaseURI, schemaElement) => {
|
|
80
|
+
if (!(0, _apidomDatamodel.isStringElement)(schemaElement.$ref)) {
|
|
18
81
|
return undefined;
|
|
19
82
|
}
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const $refBaseURI = (
|
|
23
|
-
return url.resolve(acc, url.sanitize(url.stripHash(uri)));
|
|
24
|
-
}, retrievalURI, [...ancestorsSchemaIdentifiers, (0, _apidomCore.toValue)(schemaElement.$ref)]);
|
|
83
|
+
const $ref = (0, _apidomCore.toValue)(schemaElement.$ref);
|
|
84
|
+
const hash = url.getHash($ref);
|
|
85
|
+
const $refBaseURI = resolveSchema$ids(schemaBaseURI, [$ref]);
|
|
25
86
|
return `${$refBaseURI}${hash === '#' ? '' : hash}`;
|
|
26
87
|
};
|
|
27
88
|
|
|
28
89
|
/**
|
|
90
|
+
* Resolves the `$id` of the schema against `baseURI`, the base URI in effect
|
|
91
|
+
* where the schema sits, or returns `undefined` when the schema declares no string `$id`.
|
|
92
|
+
*
|
|
29
93
|
* @public
|
|
30
94
|
*/
|
|
31
95
|
exports.resolveSchema$refField = resolveSchema$refField;
|
|
32
|
-
const resolveSchema$idField = (
|
|
33
|
-
if (
|
|
96
|
+
const resolveSchema$idField = (baseURI, schemaElement) => {
|
|
97
|
+
if (!(0, _apidomDatamodel.isStringElement)(schemaElement.$id)) {
|
|
34
98
|
return undefined;
|
|
35
99
|
}
|
|
36
|
-
|
|
37
|
-
return (0, _ramda.reduce)((acc, $id) => {
|
|
38
|
-
return url.resolve(acc, url.sanitize(url.stripHash($id)));
|
|
39
|
-
}, retrievalURI, ancestorsSchemaIdentifiers);
|
|
100
|
+
return resolveSchema$ids(baseURI, [(0, _apidomCore.toValue)(schemaElement.$id)]);
|
|
40
101
|
};
|
|
41
102
|
|
|
42
103
|
/**
|
|
@@ -1,34 +1,88 @@
|
|
|
1
1
|
import { reduce } from 'ramda';
|
|
2
|
-
import {
|
|
2
|
+
import { isString } from 'ramda-adjunct';
|
|
3
|
+
import { isPrimitiveElement, isStringElement } from '@speclynx/apidom-datamodel';
|
|
3
4
|
import { toValue } from '@speclynx/apidom-core';
|
|
5
|
+
import { isJSONSchemaElement } from '@speclynx/apidom-ns-json-schema-2020-12';
|
|
4
6
|
import { refractSchema } from '@speclynx/apidom-ns-openapi-3-1';
|
|
5
7
|
import * as url from "../../../util/url.mjs";
|
|
6
8
|
/**
|
|
9
|
+
* A schema located within a document, together with the `$id`s of the JSON Schema
|
|
10
|
+
* elements enclosing it (outermost first; the located schema's own `$id` excluded).
|
|
7
11
|
* @public
|
|
8
12
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Whether the element is a JSON Schema element declaring a `$id`.
|
|
15
|
+
*/
|
|
16
|
+
export const isJSONSchemaElementWith$id = element => isJSONSchemaElement(element) && isStringElement(element.$id);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `$id`s declared by the JSON Schema elements among `nodes`, in order.
|
|
20
|
+
*/
|
|
21
|
+
export const schema$idsOf = nodes => nodes.filter(isJSONSchemaElementWith$id).map(node => toValue(node.$id));
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `$id`s of the JSON Schema elements on `path` (outermost first, the node itself
|
|
25
|
+
* included). The `$id`s are read from the elements themselves rather than from
|
|
26
|
+
* refraction-time metadata, so a `$id` assigned after refraction (e.g. by the
|
|
27
|
+
* bundler) is honored.
|
|
28
|
+
*/
|
|
29
|
+
export const collectSchema$ids = path => schema$idsOf([...path.getAncestorNodes().reverse(), path.node]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `$id`s of the schemas enclosing a traversal root that is a fragment detached
|
|
33
|
+
* from its document, read from the `ancestorsSchemaIdentifiers` meta the JSON
|
|
34
|
+
* Schema refractors record. The meta also carries the root's own `$id`, which
|
|
35
|
+
* the ancestor walk contributes itself, so it is dropped. A detached fragment
|
|
36
|
+
* has no live ancestors, so the meta is the only carrier of its lexical
|
|
37
|
+
* context; this is the one reader of it the refractors must keep serving.
|
|
38
|
+
*/
|
|
39
|
+
export const detachedRootSchema$ids = root => {
|
|
40
|
+
const meta = root?.meta.get('ancestorsSchemaIdentifiers');
|
|
41
|
+
const $ids = Array.isArray(meta) ? meta.filter(isString) : [];
|
|
42
|
+
const own$id = isJSONSchemaElementWith$id(root) ? toValue(root.$id) : undefined;
|
|
43
|
+
return $ids.length > 0 && $ids.at(-1) === own$id ? $ids.slice(0, -1) : $ids;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolves a chain of `$id`s (outermost first) against `baseURI`, each `$id`
|
|
48
|
+
* refining the base the next one resolves against.
|
|
49
|
+
*/
|
|
50
|
+
export const resolveSchema$ids = (baseURI, $ids) => reduce((acc, $id) => url.resolve(acc, url.sanitize(url.stripHash($id))), baseURI, $ids);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolves the base URI of the schema at `path`: `baseURI` (the base URI in
|
|
54
|
+
* effect at the traversal root) refined by each `$id` on the schema's ancestor
|
|
55
|
+
* chain in turn, the schema's own `$id` included.
|
|
56
|
+
*/
|
|
57
|
+
export const resolveSchemaBaseURI = (baseURI, path) => resolveSchema$ids(baseURI, collectSchema$ids(path));
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the `$ref` of the schema against its base URI (see
|
|
61
|
+
* `resolveSchemaBaseURI`), or returns `undefined` when the schema has no string `$ref`.
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
export const resolveSchema$refField = (schemaBaseURI, schemaElement) => {
|
|
66
|
+
if (!isStringElement(schemaElement.$ref)) {
|
|
11
67
|
return undefined;
|
|
12
68
|
}
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const $refBaseURI =
|
|
16
|
-
return url.resolve(acc, url.sanitize(url.stripHash(uri)));
|
|
17
|
-
}, retrievalURI, [...ancestorsSchemaIdentifiers, toValue(schemaElement.$ref)]);
|
|
69
|
+
const $ref = toValue(schemaElement.$ref);
|
|
70
|
+
const hash = url.getHash($ref);
|
|
71
|
+
const $refBaseURI = resolveSchema$ids(schemaBaseURI, [$ref]);
|
|
18
72
|
return `${$refBaseURI}${hash === '#' ? '' : hash}`;
|
|
19
73
|
};
|
|
20
74
|
|
|
21
75
|
/**
|
|
76
|
+
* Resolves the `$id` of the schema against `baseURI`, the base URI in effect
|
|
77
|
+
* where the schema sits, or returns `undefined` when the schema declares no string `$id`.
|
|
78
|
+
*
|
|
22
79
|
* @public
|
|
23
80
|
*/
|
|
24
|
-
export const resolveSchema$idField = (
|
|
25
|
-
if (
|
|
81
|
+
export const resolveSchema$idField = (baseURI, schemaElement) => {
|
|
82
|
+
if (!isStringElement(schemaElement.$id)) {
|
|
26
83
|
return undefined;
|
|
27
84
|
}
|
|
28
|
-
|
|
29
|
-
return reduce((acc, $id) => {
|
|
30
|
-
return url.resolve(acc, url.sanitize(url.stripHash($id)));
|
|
31
|
-
}, retrievalURI, ancestorsSchemaIdentifiers);
|
|
85
|
+
return resolveSchema$ids(baseURI, [toValue(schemaElement.$id)]);
|
|
32
86
|
};
|
|
33
87
|
|
|
34
88
|
/**
|
|
@@ -16,6 +16,7 @@ var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
|
|
|
16
16
|
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-3-1");
|
|
17
17
|
var _$anchor = require("./selectors/$anchor.cjs");
|
|
18
18
|
var _uri = require("./selectors/uri.cjs");
|
|
19
|
+
var _jsonPointer = require("./selectors/json-pointer.cjs");
|
|
19
20
|
var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
|
|
20
21
|
var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
|
|
21
22
|
var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
|
|
@@ -38,8 +39,23 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
38
39
|
reference;
|
|
39
40
|
options;
|
|
40
41
|
refractCache;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Index of `$id`-bearing schemas per document, shared across nested visitors
|
|
45
|
+
* of a single dereference run so it never outlives mutations of the documents.
|
|
46
|
+
*/
|
|
47
|
+
schema$idIndex;
|
|
41
48
|
visited;
|
|
42
49
|
|
|
50
|
+
/**
|
|
51
|
+
* `$id`s of the JSON Schema elements enclosing the traversal root within its
|
|
52
|
+
* document (outermost first). Empty when the root is the document itself; a
|
|
53
|
+
* nested visitor traversing a fragment is seeded with the `$id`s of the
|
|
54
|
+
* schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
|
|
55
|
+
* against the same base as they would from the document root.
|
|
56
|
+
*/
|
|
57
|
+
ancestorSchema$ids;
|
|
58
|
+
|
|
43
59
|
/**
|
|
44
60
|
* Tracks element ancestors across dive-deep traversal boundaries.
|
|
45
61
|
* Used for cycle detection: if a referenced element is found in
|
|
@@ -51,15 +67,19 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
51
67
|
options,
|
|
52
68
|
indirections = [],
|
|
53
69
|
refractCache = new WeakMap(),
|
|
70
|
+
schema$idIndex = new WeakMap(),
|
|
54
71
|
ancestors = new _util2.AncestorLineage(),
|
|
55
|
-
visited = new WeakSet()
|
|
72
|
+
visited = new WeakSet(),
|
|
73
|
+
ancestorSchema$ids = []
|
|
56
74
|
}) {
|
|
57
75
|
this.indirections = indirections;
|
|
58
76
|
this.reference = reference;
|
|
59
77
|
this.options = options;
|
|
60
78
|
this.refractCache = refractCache;
|
|
79
|
+
this.schema$idIndex = schema$idIndex;
|
|
61
80
|
this.ancestors = new _util2.AncestorLineage(...ancestors);
|
|
62
81
|
this.visited = visited;
|
|
82
|
+
this.ancestorSchema$ids = ancestorSchema$ids;
|
|
63
83
|
}
|
|
64
84
|
toAncestorLineage(path) {
|
|
65
85
|
const ancestorNodes = path.getAncestorNodes();
|
|
@@ -318,6 +338,7 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
318
338
|
indirections: [...this.indirections],
|
|
319
339
|
options: this.options,
|
|
320
340
|
refractCache: this.refractCache,
|
|
341
|
+
schema$idIndex: this.schema$idIndex,
|
|
321
342
|
visited: this.visited,
|
|
322
343
|
ancestors: ancestorsLineage
|
|
323
344
|
});
|
|
@@ -476,6 +497,7 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
476
497
|
indirections: [...this.indirections],
|
|
477
498
|
options: this.options,
|
|
478
499
|
refractCache: this.refractCache,
|
|
500
|
+
schema$idIndex: this.schema$idIndex,
|
|
479
501
|
visited: this.visited,
|
|
480
502
|
ancestors: ancestorsLineage
|
|
481
503
|
});
|
|
@@ -673,7 +695,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
673
695
|
let {
|
|
674
696
|
uri: retrievalURI
|
|
675
697
|
} = reference;
|
|
676
|
-
|
|
698
|
+
// base URI in effect at the traversal root: the document base refined by
|
|
699
|
+
// the $ids of the schemas enclosing the root when the root is a fragment
|
|
700
|
+
const rootBaseURI = (0, _util.resolveSchema$ids)(retrievalURI, this.ancestorSchema$ids);
|
|
701
|
+
const schemaBaseURI = (0, _util.resolveSchemaBaseURI)(rootBaseURI, path);
|
|
702
|
+
const $refBaseURI = (0, _util.resolveSchema$refField)(schemaBaseURI, referencingElement);
|
|
677
703
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
678
704
|
const file = new _File.default({
|
|
679
705
|
uri: $refBaseURIStrippedHash
|
|
@@ -685,13 +711,48 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
685
711
|
|
|
686
712
|
// determining reference, proper evaluation and selection mechanism
|
|
687
713
|
let referencedElement;
|
|
714
|
+
// $ids of the schemas enclosing the referenced element within its document
|
|
715
|
+
let ancestorSchema$ids;
|
|
688
716
|
try {
|
|
689
717
|
if (isUnknownURI || isURL) {
|
|
690
718
|
// we're dealing with canonical URI or URL with possible fragment
|
|
691
719
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
692
720
|
const selector = $refBaseURI;
|
|
693
|
-
|
|
694
|
-
|
|
721
|
+
// the fragment being traversed is searched before the whole document: a
|
|
722
|
+
// fragment referenced from another document (a Response, Parameter, Path
|
|
723
|
+
// Item, ...) is refracted with its own semantics, so the Schema Objects it
|
|
724
|
+
// contains are indexed, whereas the referenced document as a whole is
|
|
725
|
+
// parsed generically and refracted as a bare JSON Schema, which leaves
|
|
726
|
+
// those Schema Objects (under `content`, `schema`, ...) unrecognized and
|
|
727
|
+
// their `$id`s out of the index
|
|
728
|
+
const documentElement = reference.value.result;
|
|
729
|
+
const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
|
|
730
|
+
const fragmentElement = (0, _apidomDatamodel.isParseResultElement)(rootNode) ? rootNode.result : rootNode;
|
|
731
|
+
try {
|
|
732
|
+
// the fragment's $ids resolve against the base its enclosing chain
|
|
733
|
+
// leaves in effect, which then also prefixes the located schema's chain
|
|
734
|
+
({
|
|
735
|
+
element: referencedElement,
|
|
736
|
+
ancestorSchema$ids
|
|
737
|
+
} = (0, _uri.locate)(selector, (0, _util.maybeRefractToSchemaElement)(fragmentElement), {
|
|
738
|
+
baseURI: rootBaseURI,
|
|
739
|
+
index: this.schema$idIndex
|
|
740
|
+
}));
|
|
741
|
+
ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
|
|
742
|
+
} catch (error) {
|
|
743
|
+
if (!(error instanceof _EvaluationJsonSchemaUriError.default) || fragmentElement === documentElement) {
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
// the document index is rooted at the document, so the located chain
|
|
747
|
+
// is already document-relative and takes no prefix
|
|
748
|
+
({
|
|
749
|
+
element: referencedElement,
|
|
750
|
+
ancestorSchema$ids
|
|
751
|
+
} = (0, _uri.locate)(selector, (0, _util.maybeRefractToSchemaElement)(documentElement), {
|
|
752
|
+
baseURI: reference.uri,
|
|
753
|
+
index: this.schema$idIndex
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
695
756
|
referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
|
|
696
757
|
|
|
697
758
|
// ignore resolving internal Schema Objects
|
|
@@ -723,7 +784,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
723
784
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
724
785
|
const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
725
786
|
const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
|
|
726
|
-
|
|
787
|
+
({
|
|
788
|
+
element: referencedElement,
|
|
789
|
+
ancestorSchema$ids
|
|
790
|
+
} = (0, _jsonPointer.locate)(referenceAsSchema, selector));
|
|
727
791
|
referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
|
|
728
792
|
}
|
|
729
793
|
} catch (error) {
|
|
@@ -750,7 +814,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
750
814
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
751
815
|
const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
|
|
752
816
|
const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
|
|
753
|
-
|
|
817
|
+
({
|
|
818
|
+
element: referencedElement,
|
|
819
|
+
ancestorSchema$ids
|
|
820
|
+
} = (0, _$anchor.locate)(selector, referenceAsSchema));
|
|
754
821
|
referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
|
|
755
822
|
} else {
|
|
756
823
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
@@ -771,7 +838,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
771
838
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
772
839
|
const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
773
840
|
const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
|
|
774
|
-
|
|
841
|
+
({
|
|
842
|
+
element: referencedElement,
|
|
843
|
+
ancestorSchema$ids
|
|
844
|
+
} = (0, _jsonPointer.locate)(referenceAsSchema, selector));
|
|
775
845
|
referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
|
|
776
846
|
}
|
|
777
847
|
} else {
|
|
@@ -828,7 +898,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
828
898
|
*/
|
|
829
899
|
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
830
900
|
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
831
|
-
|
|
901
|
+
// content transplanted into the referencing site is traversed under that
|
|
902
|
+
// site's $id chain; a fragment enclosed by a different chain has its own
|
|
903
|
+
// $refs resolved in place first, by a nested visitor seeded with that chain
|
|
904
|
+
const hasDifferentEnclosingBase = (0, _util.resolveSchema$ids)(reference.uri, ancestorSchema$ids) !== (0, _util.resolveSchema$ids)(rootBaseURI, (0, _util.schema$idsOf)(path.getAncestorNodes().reverse()));
|
|
905
|
+
if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || (0, _apidomNsOpenapi.isSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
832
906
|
if (shouldDetectCircular) {
|
|
833
907
|
this.visited.add(referencedElement);
|
|
834
908
|
}
|
|
@@ -838,8 +912,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
838
912
|
indirections: [...this.indirections],
|
|
839
913
|
options: this.options,
|
|
840
914
|
refractCache: this.refractCache,
|
|
915
|
+
schema$idIndex: this.schema$idIndex,
|
|
841
916
|
visited: this.visited,
|
|
842
|
-
ancestors: ancestorsLineage
|
|
917
|
+
ancestors: ancestorsLineage,
|
|
918
|
+
ancestorSchema$ids
|
|
843
919
|
});
|
|
844
920
|
referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
|
|
845
921
|
mutable: true,
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { propEq, none } from 'ramda';
|
|
2
2
|
import { isUndefined } from 'ramda-adjunct';
|
|
3
|
-
import { isElement, isStringElement, isObjectElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
3
|
+
import { isElement, isStringElement, isObjectElement, isParseResultElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
4
4
|
import { toValue, fixedFields, toYAML } from '@speclynx/apidom-core';
|
|
5
5
|
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
6
6
|
import { traverse, traverseAsync, find } from '@speclynx/apidom-traverse';
|
|
7
7
|
import { evaluate as jsonPointerEvaluate, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
|
|
8
8
|
import { isReferenceLikeElement, isPathItemElement, isReferenceElement, isSchemaElement, isOperationElement, isBooleanJSONSchemaElement, refract, refractReference, refractPathItem, refractOperation } from '@speclynx/apidom-ns-openapi-3-1';
|
|
9
|
-
import { isAnchor, uriToAnchor,
|
|
10
|
-
import {
|
|
9
|
+
import { isAnchor, uriToAnchor, locate as $anchorLocate } from "./selectors/$anchor.mjs";
|
|
10
|
+
import { locate as uriLocate } from "./selectors/uri.mjs";
|
|
11
|
+
import { locate as jsonPointerLocate } from "./selectors/json-pointer.mjs";
|
|
11
12
|
import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
|
|
12
13
|
import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
|
|
13
14
|
import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
|
|
@@ -16,7 +17,7 @@ import * as url from "../../../util/url.mjs";
|
|
|
16
17
|
import parse from "../../../parse/index.mjs";
|
|
17
18
|
import Reference from "../../../Reference.mjs";
|
|
18
19
|
import File from "../../../File.mjs";
|
|
19
|
-
import { resolveSchema$refField, maybeRefractToSchemaElement } from "./util.mjs";
|
|
20
|
+
import { schema$idsOf, resolveSchema$ids, resolveSchemaBaseURI, resolveSchema$refField, maybeRefractToSchemaElement } from "./util.mjs";
|
|
20
21
|
import { AncestorLineage } from "../../util.mjs";
|
|
21
22
|
/**
|
|
22
23
|
* @public
|
|
@@ -29,8 +30,23 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
29
30
|
reference;
|
|
30
31
|
options;
|
|
31
32
|
refractCache;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Index of `$id`-bearing schemas per document, shared across nested visitors
|
|
36
|
+
* of a single dereference run so it never outlives mutations of the documents.
|
|
37
|
+
*/
|
|
38
|
+
schema$idIndex;
|
|
32
39
|
visited;
|
|
33
40
|
|
|
41
|
+
/**
|
|
42
|
+
* `$id`s of the JSON Schema elements enclosing the traversal root within its
|
|
43
|
+
* document (outermost first). Empty when the root is the document itself; a
|
|
44
|
+
* nested visitor traversing a fragment is seeded with the `$id`s of the
|
|
45
|
+
* schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
|
|
46
|
+
* against the same base as they would from the document root.
|
|
47
|
+
*/
|
|
48
|
+
ancestorSchema$ids;
|
|
49
|
+
|
|
34
50
|
/**
|
|
35
51
|
* Tracks element ancestors across dive-deep traversal boundaries.
|
|
36
52
|
* Used for cycle detection: if a referenced element is found in
|
|
@@ -42,15 +58,19 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
42
58
|
options,
|
|
43
59
|
indirections = [],
|
|
44
60
|
refractCache = new WeakMap(),
|
|
61
|
+
schema$idIndex = new WeakMap(),
|
|
45
62
|
ancestors = new AncestorLineage(),
|
|
46
|
-
visited = new WeakSet()
|
|
63
|
+
visited = new WeakSet(),
|
|
64
|
+
ancestorSchema$ids = []
|
|
47
65
|
}) {
|
|
48
66
|
this.indirections = indirections;
|
|
49
67
|
this.reference = reference;
|
|
50
68
|
this.options = options;
|
|
51
69
|
this.refractCache = refractCache;
|
|
70
|
+
this.schema$idIndex = schema$idIndex;
|
|
52
71
|
this.ancestors = new AncestorLineage(...ancestors);
|
|
53
72
|
this.visited = visited;
|
|
73
|
+
this.ancestorSchema$ids = ancestorSchema$ids;
|
|
54
74
|
}
|
|
55
75
|
toAncestorLineage(path) {
|
|
56
76
|
const ancestorNodes = path.getAncestorNodes();
|
|
@@ -309,6 +329,7 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
309
329
|
indirections: [...this.indirections],
|
|
310
330
|
options: this.options,
|
|
311
331
|
refractCache: this.refractCache,
|
|
332
|
+
schema$idIndex: this.schema$idIndex,
|
|
312
333
|
visited: this.visited,
|
|
313
334
|
ancestors: ancestorsLineage
|
|
314
335
|
});
|
|
@@ -467,6 +488,7 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
467
488
|
indirections: [...this.indirections],
|
|
468
489
|
options: this.options,
|
|
469
490
|
refractCache: this.refractCache,
|
|
491
|
+
schema$idIndex: this.schema$idIndex,
|
|
470
492
|
visited: this.visited,
|
|
471
493
|
ancestors: ancestorsLineage
|
|
472
494
|
});
|
|
@@ -664,7 +686,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
664
686
|
let {
|
|
665
687
|
uri: retrievalURI
|
|
666
688
|
} = reference;
|
|
667
|
-
|
|
689
|
+
// base URI in effect at the traversal root: the document base refined by
|
|
690
|
+
// the $ids of the schemas enclosing the root when the root is a fragment
|
|
691
|
+
const rootBaseURI = resolveSchema$ids(retrievalURI, this.ancestorSchema$ids);
|
|
692
|
+
const schemaBaseURI = resolveSchemaBaseURI(rootBaseURI, path);
|
|
693
|
+
const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
|
|
668
694
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
669
695
|
const file = new File({
|
|
670
696
|
uri: $refBaseURIStrippedHash
|
|
@@ -676,13 +702,48 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
676
702
|
|
|
677
703
|
// determining reference, proper evaluation and selection mechanism
|
|
678
704
|
let referencedElement;
|
|
705
|
+
// $ids of the schemas enclosing the referenced element within its document
|
|
706
|
+
let ancestorSchema$ids;
|
|
679
707
|
try {
|
|
680
708
|
if (isUnknownURI || isURL) {
|
|
681
709
|
// we're dealing with canonical URI or URL with possible fragment
|
|
682
710
|
retrievalURI = this.toBaseURI($refBaseURI);
|
|
683
711
|
const selector = $refBaseURI;
|
|
684
|
-
|
|
685
|
-
|
|
712
|
+
// the fragment being traversed is searched before the whole document: a
|
|
713
|
+
// fragment referenced from another document (a Response, Parameter, Path
|
|
714
|
+
// Item, ...) is refracted with its own semantics, so the Schema Objects it
|
|
715
|
+
// contains are indexed, whereas the referenced document as a whole is
|
|
716
|
+
// parsed generically and refracted as a bare JSON Schema, which leaves
|
|
717
|
+
// those Schema Objects (under `content`, `schema`, ...) unrecognized and
|
|
718
|
+
// their `$id`s out of the index
|
|
719
|
+
const documentElement = reference.value.result;
|
|
720
|
+
const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
|
|
721
|
+
const fragmentElement = isParseResultElement(rootNode) ? rootNode.result : rootNode;
|
|
722
|
+
try {
|
|
723
|
+
// the fragment's $ids resolve against the base its enclosing chain
|
|
724
|
+
// leaves in effect, which then also prefixes the located schema's chain
|
|
725
|
+
({
|
|
726
|
+
element: referencedElement,
|
|
727
|
+
ancestorSchema$ids
|
|
728
|
+
} = uriLocate(selector, maybeRefractToSchemaElement(fragmentElement), {
|
|
729
|
+
baseURI: rootBaseURI,
|
|
730
|
+
index: this.schema$idIndex
|
|
731
|
+
}));
|
|
732
|
+
ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
|
|
733
|
+
} catch (error) {
|
|
734
|
+
if (!(error instanceof EvaluationJsonSchemaUriError) || fragmentElement === documentElement) {
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
// the document index is rooted at the document, so the located chain
|
|
738
|
+
// is already document-relative and takes no prefix
|
|
739
|
+
({
|
|
740
|
+
element: referencedElement,
|
|
741
|
+
ancestorSchema$ids
|
|
742
|
+
} = uriLocate(selector, maybeRefractToSchemaElement(documentElement), {
|
|
743
|
+
baseURI: reference.uri,
|
|
744
|
+
index: this.schema$idIndex
|
|
745
|
+
}));
|
|
746
|
+
}
|
|
686
747
|
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
687
748
|
|
|
688
749
|
// ignore resolving internal Schema Objects
|
|
@@ -714,7 +775,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
714
775
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
715
776
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
716
777
|
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
|
|
717
|
-
|
|
778
|
+
({
|
|
779
|
+
element: referencedElement,
|
|
780
|
+
ancestorSchema$ids
|
|
781
|
+
} = jsonPointerLocate(referenceAsSchema, selector));
|
|
718
782
|
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
719
783
|
}
|
|
720
784
|
} catch (error) {
|
|
@@ -741,7 +805,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
741
805
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
742
806
|
const selector = uriToAnchor($refBaseURI);
|
|
743
807
|
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
|
|
744
|
-
|
|
808
|
+
({
|
|
809
|
+
element: referencedElement,
|
|
810
|
+
ancestorSchema$ids
|
|
811
|
+
} = $anchorLocate(selector, referenceAsSchema));
|
|
745
812
|
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
746
813
|
} else {
|
|
747
814
|
// we're assuming here that we're dealing with JSON Pointer here
|
|
@@ -762,7 +829,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
762
829
|
reference = await this.toReference(url.unsanitize($refBaseURI));
|
|
763
830
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
764
831
|
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
|
|
765
|
-
|
|
832
|
+
({
|
|
833
|
+
element: referencedElement,
|
|
834
|
+
ancestorSchema$ids
|
|
835
|
+
} = jsonPointerLocate(referenceAsSchema, selector));
|
|
766
836
|
referencedElement = maybeRefractToSchemaElement(referencedElement);
|
|
767
837
|
}
|
|
768
838
|
} else {
|
|
@@ -819,7 +889,11 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
819
889
|
*/
|
|
820
890
|
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
821
891
|
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
822
|
-
|
|
892
|
+
// content transplanted into the referencing site is traversed under that
|
|
893
|
+
// site's $id chain; a fragment enclosed by a different chain has its own
|
|
894
|
+
// $refs resolved in place first, by a nested visitor seeded with that chain
|
|
895
|
+
const hasDifferentEnclosingBase = resolveSchema$ids(reference.uri, ancestorSchema$ids) !== resolveSchema$ids(rootBaseURI, schema$idsOf(path.getAncestorNodes().reverse()));
|
|
896
|
+
if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
823
897
|
if (shouldDetectCircular) {
|
|
824
898
|
this.visited.add(referencedElement);
|
|
825
899
|
}
|
|
@@ -829,8 +903,10 @@ class OpenAPI3_1DereferenceVisitor {
|
|
|
829
903
|
indirections: [...this.indirections],
|
|
830
904
|
options: this.options,
|
|
831
905
|
refractCache: this.refractCache,
|
|
906
|
+
schema$idIndex: this.schema$idIndex,
|
|
832
907
|
visited: this.visited,
|
|
833
|
-
ancestors: ancestorsLineage
|
|
908
|
+
ancestors: ancestorsLineage,
|
|
909
|
+
ancestorSchema$ids
|
|
834
910
|
});
|
|
835
911
|
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
836
912
|
mutable: true,
|