@speclynx/apidom-reference 4.0.2 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/apidom-reference.browser.js +15 -15
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +25 -25
- package/src/File.cjs +50 -0
- package/src/File.mjs +44 -0
- package/src/Reference.cjs +31 -0
- package/src/Reference.mjs +27 -0
- package/src/ReferenceSet.cjs +60 -0
- package/src/ReferenceSet.mjs +57 -0
- package/src/bundle/index.cjs +61 -0
- package/src/bundle/index.mjs +55 -0
- package/src/bundle/strategies/BundleStrategy.cjs +20 -0
- package/src/bundle/strategies/BundleStrategy.mjs +16 -0
- package/src/bundle/strategies/openapi-3-1/index.cjs +35 -0
- package/src/bundle/strategies/openapi-3-1/index.mjs +29 -0
- package/src/configuration/empty.cjs +9 -0
- package/src/configuration/empty.mjs +1 -0
- package/src/configuration/saturated.cjs +88 -0
- package/src/configuration/saturated.mjs +80 -0
- package/src/dereference/index.cjs +90 -0
- package/src/dereference/index.mjs +83 -0
- package/src/dereference/strategies/DereferenceStrategy.cjs +20 -0
- package/src/dereference/strategies/DereferenceStrategy.mjs +16 -0
- package/src/dereference/strategies/apidom/index.cjs +89 -0
- package/src/dereference/strategies/apidom/index.mjs +83 -0
- package/src/dereference/strategies/apidom/selectors/element-id.cjs +47 -0
- package/src/dereference/strategies/apidom/selectors/element-id.mjs +41 -0
- package/src/dereference/strategies/apidom/visitor.cjs +266 -0
- package/src/dereference/strategies/apidom/visitor.mjs +259 -0
- package/src/dereference/strategies/arazzo-1/index.cjs +109 -0
- package/src/dereference/strategies/arazzo-1/index.mjs +100 -0
- package/src/dereference/strategies/arazzo-1/selectors/$anchor.cjs +12 -0
- package/src/dereference/strategies/arazzo-1/selectors/$anchor.mjs +1 -0
- package/src/dereference/strategies/arazzo-1/selectors/uri.cjs +8 -0
- package/src/dereference/strategies/arazzo-1/selectors/uri.mjs +1 -0
- package/src/dereference/strategies/arazzo-1/source-descriptions.cjs +248 -0
- package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +243 -0
- package/src/dereference/strategies/arazzo-1/util.cjs +37 -0
- package/src/dereference/strategies/arazzo-1/util.mjs +29 -0
- package/src/dereference/strategies/arazzo-1/visitor.cjs +507 -0
- package/src/dereference/strategies/arazzo-1/visitor.mjs +500 -0
- package/src/dereference/strategies/asyncapi-2/index.cjs +94 -0
- package/src/dereference/strategies/asyncapi-2/index.mjs +88 -0
- package/src/dereference/strategies/asyncapi-2/visitor.cjs +501 -0
- package/src/dereference/strategies/asyncapi-2/visitor.mjs +494 -0
- package/src/dereference/strategies/openapi-2/index.cjs +96 -0
- package/src/dereference/strategies/openapi-2/index.mjs +90 -0
- package/src/dereference/strategies/openapi-2/visitor.cjs +629 -0
- package/src/dereference/strategies/openapi-2/visitor.mjs +622 -0
- package/src/dereference/strategies/openapi-3-0/index.cjs +96 -0
- package/src/dereference/strategies/openapi-3-0/index.mjs +90 -0
- package/src/dereference/strategies/openapi-3-0/visitor.cjs +622 -0
- package/src/dereference/strategies/openapi-3-0/visitor.mjs +615 -0
- package/src/dereference/strategies/openapi-3-1/index.cjs +99 -0
- package/src/dereference/strategies/openapi-3-1/index.mjs +90 -0
- package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +65 -0
- package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +54 -0
- package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +50 -0
- package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +42 -0
- package/src/dereference/strategies/openapi-3-1/util.cjs +68 -0
- package/src/dereference/strategies/openapi-3-1/util.mjs +59 -0
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +874 -0
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +867 -0
- package/src/dereference/util.cjs +31 -0
- package/src/dereference/util.mjs +27 -0
- package/src/errors/BundleError.cjs +10 -0
- package/src/errors/BundleError.mjs +7 -0
- package/src/errors/DereferenceError.cjs +10 -0
- package/src/errors/DereferenceError.mjs +7 -0
- package/src/errors/EvaluationElementIdError.cjs +10 -0
- package/src/errors/EvaluationElementIdError.mjs +7 -0
- package/src/errors/EvaluationJsonSchema$anchorError.cjs +11 -0
- package/src/errors/EvaluationJsonSchema$anchorError.mjs +6 -0
- package/src/errors/EvaluationJsonSchemaUriError.cjs +11 -0
- package/src/errors/EvaluationJsonSchemaUriError.mjs +6 -0
- package/src/errors/InvalidJsonSchema$anchorError.cjs +15 -0
- package/src/errors/InvalidJsonSchema$anchorError.mjs +10 -0
- package/src/errors/JsonSchema$anchorError.cjs +10 -0
- package/src/errors/JsonSchema$anchorError.mjs +7 -0
- package/src/errors/JsonSchemaUriError.cjs +10 -0
- package/src/errors/JsonSchemaUriError.mjs +7 -0
- package/src/errors/MaximumBundleDepthError.cjs +11 -0
- package/src/errors/MaximumBundleDepthError.mjs +6 -0
- package/src/errors/MaximumDereferenceDepthError.cjs +11 -0
- package/src/errors/MaximumDereferenceDepthError.mjs +6 -0
- package/src/errors/MaximumResolveDepthError.cjs +11 -0
- package/src/errors/MaximumResolveDepthError.mjs +6 -0
- package/src/errors/ParseError.cjs +10 -0
- package/src/errors/ParseError.mjs +7 -0
- package/src/errors/ParserError.cjs +11 -0
- package/src/errors/ParserError.mjs +6 -0
- package/src/errors/PluginError.cjs +18 -0
- package/src/errors/PluginError.mjs +15 -0
- package/src/errors/ResolveError.cjs +10 -0
- package/src/errors/ResolveError.mjs +7 -0
- package/src/errors/ResolverError.cjs +11 -0
- package/src/errors/ResolverError.mjs +6 -0
- package/src/errors/UnmatchedBundleStrategyError.cjs +11 -0
- package/src/errors/UnmatchedBundleStrategyError.mjs +6 -0
- package/src/errors/UnmatchedDereferenceStrategyError.cjs +11 -0
- package/src/errors/UnmatchedDereferenceStrategyError.mjs +6 -0
- package/src/errors/UnmatchedParserError.cjs +11 -0
- package/src/errors/UnmatchedParserError.mjs +6 -0
- package/src/errors/UnmatchedResolveStrategyError.cjs +11 -0
- package/src/errors/UnmatchedResolveStrategyError.mjs +6 -0
- package/src/errors/UnmatchedResolverError.cjs +11 -0
- package/src/errors/UnmatchedResolverError.mjs +6 -0
- package/src/errors/UnresolvableReferenceError.cjs +11 -0
- package/src/errors/UnresolvableReferenceError.mjs +6 -0
- package/src/index.cjs +146 -0
- package/src/index.mjs +103 -0
- package/src/options/index.cjs +194 -0
- package/src/options/index.mjs +191 -0
- package/src/options/util.cjs +24 -0
- package/src/options/util.mjs +19 -0
- package/src/parse/index.cjs +69 -0
- package/src/parse/index.mjs +63 -0
- package/src/parse/parsers/Parser.cjs +62 -0
- package/src/parse/parsers/Parser.mjs +58 -0
- package/src/parse/parsers/apidom-json/index.cjs +70 -0
- package/src/parse/parsers/apidom-json/index.mjs +64 -0
- package/src/parse/parsers/arazzo-json-1/index.cjs +62 -0
- package/src/parse/parsers/arazzo-json-1/index.mjs +56 -0
- package/src/parse/parsers/arazzo-json-1/source-descriptions.cjs +221 -0
- package/src/parse/parsers/arazzo-json-1/source-descriptions.mjs +214 -0
- package/src/parse/parsers/arazzo-yaml-1/index.cjs +62 -0
- package/src/parse/parsers/arazzo-yaml-1/index.mjs +56 -0
- package/src/parse/parsers/arazzo-yaml-1/source-descriptions.cjs +12 -0
- package/src/parse/parsers/arazzo-yaml-1/source-descriptions.mjs +7 -0
- package/src/parse/parsers/asyncapi-json-2/index.cjs +54 -0
- package/src/parse/parsers/asyncapi-json-2/index.mjs +48 -0
- package/src/parse/parsers/asyncapi-yaml-2/index.cjs +54 -0
- package/src/parse/parsers/asyncapi-yaml-2/index.mjs +48 -0
- package/src/parse/parsers/binary/index-browser.cjs +56 -0
- package/src/parse/parsers/binary/index-browser.mjs +50 -0
- package/src/parse/parsers/binary/index-node.cjs +51 -0
- package/src/parse/parsers/binary/index-node.mjs +45 -0
- package/src/parse/parsers/json/index.cjs +53 -0
- package/src/parse/parsers/json/index.mjs +47 -0
- package/src/parse/parsers/openapi-json-2/index.cjs +54 -0
- package/src/parse/parsers/openapi-json-2/index.mjs +48 -0
- package/src/parse/parsers/openapi-json-3-0/index.cjs +54 -0
- package/src/parse/parsers/openapi-json-3-0/index.mjs +48 -0
- package/src/parse/parsers/openapi-json-3-1/index.cjs +54 -0
- package/src/parse/parsers/openapi-json-3-1/index.mjs +48 -0
- package/src/parse/parsers/openapi-yaml-2/index.cjs +54 -0
- package/src/parse/parsers/openapi-yaml-2/index.mjs +48 -0
- package/src/parse/parsers/openapi-yaml-3-0/index.cjs +54 -0
- package/src/parse/parsers/openapi-yaml-3-0/index.mjs +48 -0
- package/src/parse/parsers/openapi-yaml-3-1/index.cjs +54 -0
- package/src/parse/parsers/openapi-yaml-3-1/index.mjs +48 -0
- package/src/parse/parsers/yaml-1-2/index.cjs +56 -0
- package/src/parse/parsers/yaml-1-2/index.mjs +50 -0
- package/src/resolve/index.cjs +67 -0
- package/src/resolve/index.mjs +60 -0
- package/src/resolve/resolvers/HTTPResolver.cjs +45 -0
- package/src/resolve/resolvers/HTTPResolver.mjs +37 -0
- package/src/resolve/resolvers/Resolver.cjs +20 -0
- package/src/resolve/resolvers/Resolver.mjs +16 -0
- package/src/resolve/resolvers/file/index-browser.cjs +24 -0
- package/src/resolve/resolvers/file/index-browser.mjs +19 -0
- package/src/resolve/resolvers/file/index-node.cjs +49 -0
- package/src/resolve/resolvers/file/index-node.mjs +42 -0
- package/src/resolve/resolvers/http-axios/cache/MemoryCache.cjs +41 -0
- package/src/resolve/resolvers/http-axios/cache/MemoryCache.mjs +37 -0
- package/src/resolve/resolvers/http-axios/index.cjs +113 -0
- package/src/resolve/resolvers/http-axios/index.mjs +105 -0
- package/src/resolve/strategies/ResolveStrategy.cjs +20 -0
- package/src/resolve/strategies/ResolveStrategy.mjs +16 -0
- package/src/resolve/strategies/apidom/index.cjs +49 -0
- package/src/resolve/strategies/apidom/index.mjs +43 -0
- package/src/resolve/strategies/asyncapi-2/index.cjs +49 -0
- package/src/resolve/strategies/asyncapi-2/index.mjs +43 -0
- package/src/resolve/strategies/openapi-2/index.cjs +49 -0
- package/src/resolve/strategies/openapi-2/index.mjs +43 -0
- package/src/resolve/strategies/openapi-3-0/index.cjs +49 -0
- package/src/resolve/strategies/openapi-3-0/index.mjs +43 -0
- package/src/resolve/strategies/openapi-3-1/index.cjs +49 -0
- package/src/resolve/strategies/openapi-3-1/index.mjs +43 -0
- package/src/resolve/util.cjs +37 -0
- package/src/resolve/util.mjs +30 -0
- package/src/util/plugins.cjs +39 -0
- package/src/util/plugins.mjs +34 -0
- package/src/util/url.cjs +288 -0
- package/src/util/url.mjs +274 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { cloneDeep } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { traverseAsync } from '@speclynx/apidom-traverse';
|
|
3
|
+
import { isArazzoSpecification1Element, mediaTypes } from '@speclynx/apidom-ns-arazzo-1';
|
|
4
|
+
import DereferenceStrategy from "../DereferenceStrategy.mjs";
|
|
5
|
+
import Reference from "../../../Reference.mjs";
|
|
6
|
+
import ReferenceSet from "../../../ReferenceSet.mjs";
|
|
7
|
+
import Arazzo1DereferenceVisitor from "./visitor.mjs";
|
|
8
|
+
import { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
class Arazzo1DereferenceStrategy extends DereferenceStrategy {
|
|
16
|
+
constructor(options) {
|
|
17
|
+
super({
|
|
18
|
+
...(options ?? {}),
|
|
19
|
+
name: 'arazzo-1'
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
canDereference(file) {
|
|
23
|
+
// assert by media type
|
|
24
|
+
if (file.mediaType !== 'text/plain') {
|
|
25
|
+
return mediaTypes.includes(file.mediaType);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// assert by inspecting ApiDOM
|
|
29
|
+
return isArazzoSpecification1Element(file.parseResult?.result);
|
|
30
|
+
}
|
|
31
|
+
async dereference(file, options) {
|
|
32
|
+
const immutableRefSet = options.dereference.refSet ?? new ReferenceSet();
|
|
33
|
+
const mutableRefSet = new ReferenceSet();
|
|
34
|
+
let refSet = immutableRefSet;
|
|
35
|
+
let reference;
|
|
36
|
+
if (!immutableRefSet.has(file.uri)) {
|
|
37
|
+
reference = new Reference({
|
|
38
|
+
uri: file.uri,
|
|
39
|
+
value: file.parseResult
|
|
40
|
+
});
|
|
41
|
+
immutableRefSet.add(reference);
|
|
42
|
+
} else {
|
|
43
|
+
// pre-computed refSet was provided as configuration option
|
|
44
|
+
reference = immutableRefSet.find(ref => ref.uri === file.uri);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clone refSet due the dereferencing process being mutable.
|
|
49
|
+
* We don't want to mutate the original refSet and the references.
|
|
50
|
+
*/
|
|
51
|
+
if (options.dereference.immutable) {
|
|
52
|
+
immutableRefSet.refs.map(ref => new Reference({
|
|
53
|
+
...ref,
|
|
54
|
+
value: cloneDeep(ref.value)
|
|
55
|
+
})).forEach(ref => mutableRefSet.add(ref));
|
|
56
|
+
reference = mutableRefSet.find(ref => ref.uri === file.uri);
|
|
57
|
+
refSet = mutableRefSet;
|
|
58
|
+
}
|
|
59
|
+
const visitor = new Arazzo1DereferenceVisitor({
|
|
60
|
+
reference,
|
|
61
|
+
options
|
|
62
|
+
});
|
|
63
|
+
const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {
|
|
64
|
+
mutable: true
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Dereference source descriptions if option is enabled.
|
|
69
|
+
*/
|
|
70
|
+
const shouldDereferenceSourceDescriptions = options?.dereference?.strategyOpts?.[this.name]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
71
|
+
if (shouldDereferenceSourceDescriptions) {
|
|
72
|
+
const sourceDescriptions = await dereferenceSourceDescriptions(dereferencedElement, reference.uri, options, this.name);
|
|
73
|
+
dereferencedElement.push(...sourceDescriptions);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* If immutable option is set, replay refs from the refSet.
|
|
78
|
+
*/
|
|
79
|
+
if (options.dereference.immutable) {
|
|
80
|
+
mutableRefSet.refs.filter(ref => ref.uri.startsWith('immutable://')).map(ref => new Reference({
|
|
81
|
+
...ref,
|
|
82
|
+
uri: ref.uri.replace(/^immutable:\/\//, '')
|
|
83
|
+
})).forEach(ref => immutableRefSet.add(ref));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Release all memory if this refSet was not provided as a configuration option.
|
|
88
|
+
* If provided as configuration option, then provider is responsible for cleanup.
|
|
89
|
+
*/
|
|
90
|
+
if (options.dereference.refSet === null) {
|
|
91
|
+
immutableRefSet.clean();
|
|
92
|
+
}
|
|
93
|
+
mutableRefSet.clean();
|
|
94
|
+
return dereferencedElement;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export { Arazzo1DereferenceVisitor };
|
|
98
|
+
export { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement } from "./util.mjs";
|
|
99
|
+
export { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
|
|
100
|
+
export default Arazzo1DereferenceStrategy;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.uriToAnchor = exports.parse = exports.isAnchor = exports.evaluate = exports.JsonSchema$anchorError = exports.InvalidJsonSchema$anchorError = exports.EvaluationJsonSchema$anchorError = void 0;
|
|
5
|
+
var _$anchor = require("../../openapi-3-1/selectors/$anchor.cjs");
|
|
6
|
+
exports.isAnchor = _$anchor.isAnchor;
|
|
7
|
+
exports.uriToAnchor = _$anchor.uriToAnchor;
|
|
8
|
+
exports.parse = _$anchor.parse;
|
|
9
|
+
exports.evaluate = _$anchor.evaluate;
|
|
10
|
+
exports.EvaluationJsonSchema$anchorError = _$anchor.EvaluationJsonSchema$anchorError;
|
|
11
|
+
exports.InvalidJsonSchema$anchorError = _$anchor.InvalidJsonSchema$anchorError;
|
|
12
|
+
exports.JsonSchema$anchorError = _$anchor.JsonSchema$anchorError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { isAnchor, uriToAnchor, parse, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.evaluate = exports.JsonSchemaUriError = exports.EvaluationJsonSchemaUriError = void 0;
|
|
5
|
+
var _uri = require("../../openapi-3-1/selectors/uri.cjs");
|
|
6
|
+
exports.evaluate = _uri.evaluate;
|
|
7
|
+
exports.EvaluationJsonSchemaUriError = _uri.EvaluationJsonSchemaUriError;
|
|
8
|
+
exports.JsonSchemaUriError = _uri.JsonSchemaUriError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.dereferenceSourceDescriptions = dereferenceSourceDescriptions;
|
|
6
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
7
|
+
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
8
|
+
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
9
|
+
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
10
|
+
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
11
|
+
var _apidomCore = require("@speclynx/apidom-core");
|
|
12
|
+
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
13
|
+
var _util = require("../../../options/util.cjs");
|
|
14
|
+
var _index = _interopRequireWildcard(require("../../index.cjs"));
|
|
15
|
+
/**
|
|
16
|
+
* Dereferences a single source description element.
|
|
17
|
+
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
18
|
+
*/
|
|
19
|
+
async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
20
|
+
const parseResult = new _apidomDatamodel.ParseResultElement();
|
|
21
|
+
if (!(0, _apidomNsArazzo.isSourceDescriptionElement)(sourceDescription)) {
|
|
22
|
+
const annotation = new _apidomDatamodel.AnnotationElement('Element is not a valid SourceDescriptionElement. Skipping');
|
|
23
|
+
annotation.classes.push('warning');
|
|
24
|
+
parseResult.push(annotation);
|
|
25
|
+
return parseResult;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// set class and metadata from source description element
|
|
29
|
+
parseResult.classes.push('source-description');
|
|
30
|
+
if ((0, _apidomDatamodel.isStringElement)(sourceDescription.name)) parseResult.setMetaProperty('name', (0, _apidomCore.toValue)(sourceDescription.name));
|
|
31
|
+
if ((0, _apidomDatamodel.isStringElement)(sourceDescription.type)) parseResult.setMetaProperty('type', (0, _apidomCore.toValue)(sourceDescription.type));
|
|
32
|
+
const sourceDescriptionURI = (0, _apidomCore.toValue)(sourceDescription.url);
|
|
33
|
+
if (typeof sourceDescriptionURI !== 'string') {
|
|
34
|
+
const annotation = new _apidomDatamodel.AnnotationElement('Source description URL is missing or not a string. Skipping');
|
|
35
|
+
annotation.classes.push('warning');
|
|
36
|
+
parseResult.push(annotation);
|
|
37
|
+
return parseResult;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// normalize URI for consistent cycle detection and refSet cache key matching
|
|
41
|
+
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
42
|
+
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
43
|
+
|
|
44
|
+
// skip if already visited (cycle detection)
|
|
45
|
+
if (ctx.visitedUrls.has(retrievalURI)) {
|
|
46
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
47
|
+
annotation.classes.push('warning');
|
|
48
|
+
parseResult.push(annotation);
|
|
49
|
+
return parseResult;
|
|
50
|
+
}
|
|
51
|
+
ctx.visitedUrls.add(retrievalURI);
|
|
52
|
+
|
|
53
|
+
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
54
|
+
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
55
|
+
try {
|
|
56
|
+
let sourceDescriptionDereferenced;
|
|
57
|
+
if ((0, _apidomDatamodel.isParseResultElement)(existingParseResult)) {
|
|
58
|
+
// use existing parsed result - just dereference it (no re-fetch/re-parse)
|
|
59
|
+
sourceDescriptionDereferenced = await (0, _index.dereferenceApiDOM)(existingParseResult, (0, _util.merge)(ctx.options, {
|
|
60
|
+
parse: {
|
|
61
|
+
mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
|
|
62
|
+
},
|
|
63
|
+
resolve: {
|
|
64
|
+
baseURI: retrievalURI
|
|
65
|
+
},
|
|
66
|
+
dereference: {
|
|
67
|
+
strategyOpts: {
|
|
68
|
+
// nested documents should dereference all their source descriptions
|
|
69
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
70
|
+
// set at strategy-specific level to override any inherited filters
|
|
71
|
+
[ctx.strategyName]: {
|
|
72
|
+
sourceDescriptions: true,
|
|
73
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
74
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
} else {
|
|
80
|
+
// no existing parse result - fetch, parse, and dereference
|
|
81
|
+
sourceDescriptionDereferenced = await (0, _index.default)(retrievalURI, (0, _util.merge)(ctx.options, {
|
|
82
|
+
parse: {
|
|
83
|
+
mediaType: 'text/plain' // allow parser plugin detection
|
|
84
|
+
},
|
|
85
|
+
dereference: {
|
|
86
|
+
strategyOpts: {
|
|
87
|
+
// nested documents should dereference all their source descriptions
|
|
88
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
89
|
+
// set at strategy-specific level to override any inherited filters
|
|
90
|
+
[ctx.strategyName]: {
|
|
91
|
+
sourceDescriptions: true,
|
|
92
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
93
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// merge dereferenced result into our parse result
|
|
101
|
+
for (const item of sourceDescriptionDereferenced) {
|
|
102
|
+
parseResult.push(item);
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// create error annotation instead of failing entire dereference
|
|
106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
107
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Error dereferencing source description "${retrievalURI}": ${message}`);
|
|
108
|
+
annotation.classes.push('error');
|
|
109
|
+
parseResult.push(annotation);
|
|
110
|
+
return parseResult;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// only allow OpenAPI and Arazzo as source descriptions
|
|
114
|
+
const {
|
|
115
|
+
api: sourceDescriptionAPI
|
|
116
|
+
} = parseResult;
|
|
117
|
+
const isOpenApi = (0, _apidomNsOpenapi.isSwaggerElement)(sourceDescriptionAPI) || (0, _apidomNsOpenapi2.isOpenApi3_0Element)(sourceDescriptionAPI) || (0, _apidomNsOpenapi3.isOpenApi3_1Element)(sourceDescriptionAPI);
|
|
118
|
+
const isArazzo = (0, _apidomNsArazzo.isArazzoSpecification1Element)(sourceDescriptionAPI);
|
|
119
|
+
if (!isOpenApi && !isArazzo) {
|
|
120
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
121
|
+
annotation.classes.push('warning');
|
|
122
|
+
parseResult.push(annotation);
|
|
123
|
+
return parseResult;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// validate declared type matches actual dereferenced type
|
|
127
|
+
const declaredType = (0, _apidomCore.toValue)(sourceDescription.type);
|
|
128
|
+
if (typeof declaredType === 'string') {
|
|
129
|
+
if (declaredType === 'openapi' && !isOpenApi) {
|
|
130
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but dereferenced as Arazzo document`);
|
|
131
|
+
annotation.classes.push('warning');
|
|
132
|
+
parseResult.push(annotation);
|
|
133
|
+
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
134
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but dereferenced as OpenAPI document`);
|
|
135
|
+
annotation.classes.push('warning');
|
|
136
|
+
parseResult.push(annotation);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return parseResult;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Dereferences source descriptions from an Arazzo document.
|
|
144
|
+
*
|
|
145
|
+
* Each source description result is attached to its corresponding
|
|
146
|
+
* SourceDescriptionElement's meta as 'parseResult' for easy access,
|
|
147
|
+
* regardless of success or failure. On failure, the ParseResultElement
|
|
148
|
+
* contains annotations explaining what went wrong.
|
|
149
|
+
*
|
|
150
|
+
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
151
|
+
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
152
|
+
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
153
|
+
* in `dereference.strategyOpts` to filter which source descriptions to process.
|
|
154
|
+
* @param strategyName - Strategy name for options lookup (defaults to 'arazzo-1')
|
|
155
|
+
* @returns Array of ParseResultElements. Returns one ParseResultElement per source description
|
|
156
|
+
* (each with class 'source-description' and metadata: name, type, retrievalURI).
|
|
157
|
+
* May return early with a single-element array containing a warning annotation when:
|
|
158
|
+
* - The API is not an Arazzo specification
|
|
159
|
+
* - The sourceDescriptions field is missing or not an array
|
|
160
|
+
* - Maximum dereference depth is exceeded (error annotation)
|
|
161
|
+
* Returns an empty array when no source description names match the filter.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* import { toValue } from '@speclynx/apidom-core';
|
|
166
|
+
*
|
|
167
|
+
* // Dereference all source descriptions
|
|
168
|
+
* await dereferenceSourceDescriptions(parseResult, uri, options);
|
|
169
|
+
*
|
|
170
|
+
* // Filter by name
|
|
171
|
+
* await dereferenceSourceDescriptions(parseResult, uri, mergeOptions(options, {
|
|
172
|
+
* dereference: { strategyOpts: { sourceDescriptions: ['petStore'] } },
|
|
173
|
+
* }));
|
|
174
|
+
*
|
|
175
|
+
* // Access dereferenced document from source description element
|
|
176
|
+
* const sourceDesc = parseResult.api.sourceDescriptions.get(0);
|
|
177
|
+
* const dereferencedDoc = sourceDesc.meta.get('parseResult');
|
|
178
|
+
* const retrievalURI = toValue(dereferencedDoc.meta.get('retrievalURI'));
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* @public
|
|
182
|
+
*/
|
|
183
|
+
async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalURI, options, strategyName = 'arazzo-1') {
|
|
184
|
+
const baseURI = url.sanitize(url.stripHash(parseResultRetrievalURI));
|
|
185
|
+
const results = [];
|
|
186
|
+
|
|
187
|
+
// get API from dereferenced parse result
|
|
188
|
+
const {
|
|
189
|
+
api
|
|
190
|
+
} = parseResult;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validate prerequisites for dereferencing source descriptions.
|
|
194
|
+
* Return warning annotations if validation fails.
|
|
195
|
+
*/
|
|
196
|
+
if (!(0, _apidomNsArazzo.isArazzoSpecification1Element)(api)) {
|
|
197
|
+
const annotation = new _apidomDatamodel.AnnotationElement('Cannot dereference source descriptions: API is not an Arazzo specification');
|
|
198
|
+
annotation.classes.push('warning');
|
|
199
|
+
return [new _apidomDatamodel.ParseResultElement([annotation])];
|
|
200
|
+
}
|
|
201
|
+
if (!(0, _apidomDatamodel.isArrayElement)(api.sourceDescriptions)) {
|
|
202
|
+
const annotation = new _apidomDatamodel.AnnotationElement('Cannot dereference source descriptions: sourceDescriptions field is missing or not an array');
|
|
203
|
+
annotation.classes.push('warning');
|
|
204
|
+
return [new _apidomDatamodel.ParseResultElement([annotation])];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// user config: strategy-specific options take precedence over global strategyOpts
|
|
208
|
+
const maxDepth = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptionsMaxDepth ?? options?.dereference?.strategyOpts?.sourceDescriptionsMaxDepth ?? +Infinity;
|
|
209
|
+
|
|
210
|
+
// recursion state comes from strategy-specific options
|
|
211
|
+
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
212
|
+
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
213
|
+
const visitedUrls = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
|
|
214
|
+
|
|
215
|
+
// add current file to visited URLs to prevent cycles
|
|
216
|
+
visitedUrls.add(baseURI);
|
|
217
|
+
if (currentDepth >= maxDepth) {
|
|
218
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${baseURI}"`);
|
|
219
|
+
annotation.classes.push('error');
|
|
220
|
+
const parseResult = new _apidomDatamodel.ParseResultElement([annotation]);
|
|
221
|
+
parseResult.classes.push('source-description');
|
|
222
|
+
return [parseResult];
|
|
223
|
+
}
|
|
224
|
+
const ctx = {
|
|
225
|
+
baseURI,
|
|
226
|
+
options,
|
|
227
|
+
strategyName,
|
|
228
|
+
currentDepth,
|
|
229
|
+
visitedUrls
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// determine which source descriptions to dereference (array filters by name)
|
|
233
|
+
const sourceDescriptionsOption = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
234
|
+
const sourceDescriptions = Array.isArray(sourceDescriptionsOption) ? api.sourceDescriptions.filter(sd => {
|
|
235
|
+
if (!(0, _apidomNsArazzo.isSourceDescriptionElement)(sd)) return false;
|
|
236
|
+
const name = (0, _apidomCore.toValue)(sd.name);
|
|
237
|
+
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
238
|
+
}) : api.sourceDescriptions;
|
|
239
|
+
|
|
240
|
+
// process sequentially to ensure proper cycle detection with shared visitedUrls
|
|
241
|
+
for (const sourceDescription of sourceDescriptions) {
|
|
242
|
+
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
243
|
+
// always attach result (even on failure - contains annotations)
|
|
244
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult);
|
|
245
|
+
results.push(sourceDescriptionDereferenceResult);
|
|
246
|
+
}
|
|
247
|
+
return results;
|
|
248
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement, isParseResultElement } from '@speclynx/apidom-datamodel';
|
|
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
|
+
import { toValue } from '@speclynx/apidom-core';
|
|
7
|
+
import * as url from "../../../util/url.mjs";
|
|
8
|
+
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
9
|
+
import dereference, { dereferenceApiDOM } from "../../index.mjs";
|
|
10
|
+
/**
|
|
11
|
+
* Dereferences a single source description element.
|
|
12
|
+
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
13
|
+
*/
|
|
14
|
+
async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
15
|
+
const parseResult = new ParseResultElement();
|
|
16
|
+
if (!isSourceDescriptionElement(sourceDescription)) {
|
|
17
|
+
const annotation = new AnnotationElement('Element is not a valid SourceDescriptionElement. Skipping');
|
|
18
|
+
annotation.classes.push('warning');
|
|
19
|
+
parseResult.push(annotation);
|
|
20
|
+
return parseResult;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// set class and metadata from source description element
|
|
24
|
+
parseResult.classes.push('source-description');
|
|
25
|
+
if (isStringElement(sourceDescription.name)) parseResult.setMetaProperty('name', toValue(sourceDescription.name));
|
|
26
|
+
if (isStringElement(sourceDescription.type)) parseResult.setMetaProperty('type', toValue(sourceDescription.type));
|
|
27
|
+
const sourceDescriptionURI = toValue(sourceDescription.url);
|
|
28
|
+
if (typeof sourceDescriptionURI !== 'string') {
|
|
29
|
+
const annotation = new AnnotationElement('Source description URL is missing or not a string. Skipping');
|
|
30
|
+
annotation.classes.push('warning');
|
|
31
|
+
parseResult.push(annotation);
|
|
32
|
+
return parseResult;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// normalize URI for consistent cycle detection and refSet cache key matching
|
|
36
|
+
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
37
|
+
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
38
|
+
|
|
39
|
+
// skip if already visited (cycle detection)
|
|
40
|
+
if (ctx.visitedUrls.has(retrievalURI)) {
|
|
41
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
42
|
+
annotation.classes.push('warning');
|
|
43
|
+
parseResult.push(annotation);
|
|
44
|
+
return parseResult;
|
|
45
|
+
}
|
|
46
|
+
ctx.visitedUrls.add(retrievalURI);
|
|
47
|
+
|
|
48
|
+
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
49
|
+
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
50
|
+
try {
|
|
51
|
+
let sourceDescriptionDereferenced;
|
|
52
|
+
if (isParseResultElement(existingParseResult)) {
|
|
53
|
+
// use existing parsed result - just dereference it (no re-fetch/re-parse)
|
|
54
|
+
sourceDescriptionDereferenced = await dereferenceApiDOM(existingParseResult, mergeOptions(ctx.options, {
|
|
55
|
+
parse: {
|
|
56
|
+
mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
|
|
57
|
+
},
|
|
58
|
+
resolve: {
|
|
59
|
+
baseURI: retrievalURI
|
|
60
|
+
},
|
|
61
|
+
dereference: {
|
|
62
|
+
strategyOpts: {
|
|
63
|
+
// nested documents should dereference all their source descriptions
|
|
64
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
65
|
+
// set at strategy-specific level to override any inherited filters
|
|
66
|
+
[ctx.strategyName]: {
|
|
67
|
+
sourceDescriptions: true,
|
|
68
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
69
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}));
|
|
74
|
+
} else {
|
|
75
|
+
// no existing parse result - fetch, parse, and dereference
|
|
76
|
+
sourceDescriptionDereferenced = await dereference(retrievalURI, mergeOptions(ctx.options, {
|
|
77
|
+
parse: {
|
|
78
|
+
mediaType: 'text/plain' // allow parser plugin detection
|
|
79
|
+
},
|
|
80
|
+
dereference: {
|
|
81
|
+
strategyOpts: {
|
|
82
|
+
// nested documents should dereference all their source descriptions
|
|
83
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
84
|
+
// set at strategy-specific level to override any inherited filters
|
|
85
|
+
[ctx.strategyName]: {
|
|
86
|
+
sourceDescriptions: true,
|
|
87
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
88
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// merge dereferenced result into our parse result
|
|
96
|
+
for (const item of sourceDescriptionDereferenced) {
|
|
97
|
+
parseResult.push(item);
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// create error annotation instead of failing entire dereference
|
|
101
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
+
const annotation = new AnnotationElement(`Error dereferencing source description "${retrievalURI}": ${message}`);
|
|
103
|
+
annotation.classes.push('error');
|
|
104
|
+
parseResult.push(annotation);
|
|
105
|
+
return parseResult;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// only allow OpenAPI and Arazzo as source descriptions
|
|
109
|
+
const {
|
|
110
|
+
api: sourceDescriptionAPI
|
|
111
|
+
} = parseResult;
|
|
112
|
+
const isOpenApi = isSwaggerElement(sourceDescriptionAPI) || isOpenApi3_0Element(sourceDescriptionAPI) || isOpenApi3_1Element(sourceDescriptionAPI);
|
|
113
|
+
const isArazzo = isArazzoSpecification1Element(sourceDescriptionAPI);
|
|
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
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return parseResult;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Dereferences source descriptions from an Arazzo document.
|
|
139
|
+
*
|
|
140
|
+
* Each source description result is attached to its corresponding
|
|
141
|
+
* SourceDescriptionElement's meta as 'parseResult' for easy access,
|
|
142
|
+
* regardless of success or failure. On failure, the ParseResultElement
|
|
143
|
+
* contains annotations explaining what went wrong.
|
|
144
|
+
*
|
|
145
|
+
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
146
|
+
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
147
|
+
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
148
|
+
* in `dereference.strategyOpts` to filter which source descriptions to process.
|
|
149
|
+
* @param strategyName - Strategy name for options lookup (defaults to 'arazzo-1')
|
|
150
|
+
* @returns Array of ParseResultElements. Returns one ParseResultElement per source description
|
|
151
|
+
* (each with class 'source-description' and metadata: name, type, retrievalURI).
|
|
152
|
+
* May return early with a single-element array containing a warning annotation when:
|
|
153
|
+
* - The API is not an Arazzo specification
|
|
154
|
+
* - The sourceDescriptions field is missing or not an array
|
|
155
|
+
* - Maximum dereference depth is exceeded (error annotation)
|
|
156
|
+
* Returns an empty array when no source description names match the filter.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* import { toValue } from '@speclynx/apidom-core';
|
|
161
|
+
*
|
|
162
|
+
* // Dereference all source descriptions
|
|
163
|
+
* await dereferenceSourceDescriptions(parseResult, uri, options);
|
|
164
|
+
*
|
|
165
|
+
* // Filter by name
|
|
166
|
+
* await dereferenceSourceDescriptions(parseResult, uri, mergeOptions(options, {
|
|
167
|
+
* dereference: { strategyOpts: { sourceDescriptions: ['petStore'] } },
|
|
168
|
+
* }));
|
|
169
|
+
*
|
|
170
|
+
* // Access dereferenced document from source description element
|
|
171
|
+
* const sourceDesc = parseResult.api.sourceDescriptions.get(0);
|
|
172
|
+
* const dereferencedDoc = sourceDesc.meta.get('parseResult');
|
|
173
|
+
* const retrievalURI = toValue(dereferencedDoc.meta.get('retrievalURI'));
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @public
|
|
177
|
+
*/
|
|
178
|
+
export async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalURI, options, strategyName = 'arazzo-1') {
|
|
179
|
+
const baseURI = url.sanitize(url.stripHash(parseResultRetrievalURI));
|
|
180
|
+
const results = [];
|
|
181
|
+
|
|
182
|
+
// get API from dereferenced parse result
|
|
183
|
+
const {
|
|
184
|
+
api
|
|
185
|
+
} = parseResult;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate prerequisites for dereferencing source descriptions.
|
|
189
|
+
* Return warning annotations if validation fails.
|
|
190
|
+
*/
|
|
191
|
+
if (!isArazzoSpecification1Element(api)) {
|
|
192
|
+
const annotation = new AnnotationElement('Cannot dereference source descriptions: API is not an Arazzo specification');
|
|
193
|
+
annotation.classes.push('warning');
|
|
194
|
+
return [new ParseResultElement([annotation])];
|
|
195
|
+
}
|
|
196
|
+
if (!isArrayElement(api.sourceDescriptions)) {
|
|
197
|
+
const annotation = new AnnotationElement('Cannot dereference source descriptions: sourceDescriptions field is missing or not an array');
|
|
198
|
+
annotation.classes.push('warning');
|
|
199
|
+
return [new ParseResultElement([annotation])];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// user config: strategy-specific options take precedence over global strategyOpts
|
|
203
|
+
const maxDepth = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptionsMaxDepth ?? options?.dereference?.strategyOpts?.sourceDescriptionsMaxDepth ?? +Infinity;
|
|
204
|
+
|
|
205
|
+
// recursion state comes from strategy-specific options
|
|
206
|
+
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
207
|
+
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
208
|
+
const visitedUrls = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
|
|
209
|
+
|
|
210
|
+
// add current file to visited URLs to prevent cycles
|
|
211
|
+
visitedUrls.add(baseURI);
|
|
212
|
+
if (currentDepth >= maxDepth) {
|
|
213
|
+
const annotation = new AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${baseURI}"`);
|
|
214
|
+
annotation.classes.push('error');
|
|
215
|
+
const parseResult = new ParseResultElement([annotation]);
|
|
216
|
+
parseResult.classes.push('source-description');
|
|
217
|
+
return [parseResult];
|
|
218
|
+
}
|
|
219
|
+
const ctx = {
|
|
220
|
+
baseURI,
|
|
221
|
+
options,
|
|
222
|
+
strategyName,
|
|
223
|
+
currentDepth,
|
|
224
|
+
visitedUrls
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// determine which source descriptions to dereference (array filters by name)
|
|
228
|
+
const sourceDescriptionsOption = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
229
|
+
const sourceDescriptions = Array.isArray(sourceDescriptionsOption) ? api.sourceDescriptions.filter(sd => {
|
|
230
|
+
if (!isSourceDescriptionElement(sd)) return false;
|
|
231
|
+
const name = toValue(sd.name);
|
|
232
|
+
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
233
|
+
}) : api.sourceDescriptions;
|
|
234
|
+
|
|
235
|
+
// process sequentially to ensure proper cycle detection with shared visitedUrls
|
|
236
|
+
for (const sourceDescription of sourceDescriptions) {
|
|
237
|
+
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
238
|
+
// always attach result (even on failure - contains annotations)
|
|
239
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult);
|
|
240
|
+
results.push(sourceDescriptionDereferenceResult);
|
|
241
|
+
}
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.resolveSchema$refField = exports.resolveSchema$idField = exports.refractToJSONSchemaElement = exports.maybeRefractToJSONSchemaElement = void 0;
|
|
5
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
6
|
+
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
7
|
+
var _util = require("../openapi-3-1/util.cjs");
|
|
8
|
+
exports.resolveSchema$refField = _util.resolveSchema$refField;
|
|
9
|
+
exports.resolveSchema$idField = _util.resolveSchema$idField;
|
|
10
|
+
/**
|
|
11
|
+
* Cached version of JSONSchemaElement.refract.
|
|
12
|
+
*/
|
|
13
|
+
const refractToJSONSchemaElement = element => {
|
|
14
|
+
if (refractToJSONSchemaElement.cache.has(element)) {
|
|
15
|
+
return refractToJSONSchemaElement.cache.get(element);
|
|
16
|
+
}
|
|
17
|
+
const refracted = (0, _apidomNsArazzo.refractJSONSchema)(element);
|
|
18
|
+
refractToJSONSchemaElement.cache.set(element, refracted);
|
|
19
|
+
return refracted;
|
|
20
|
+
};
|
|
21
|
+
exports.refractToJSONSchemaElement = refractToJSONSchemaElement;
|
|
22
|
+
refractToJSONSchemaElement.cache = new WeakMap();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
const maybeRefractToJSONSchemaElement = element => {
|
|
28
|
+
/**
|
|
29
|
+
* Conditional version of refractToJSONSchemaElement, that acts as an identity
|
|
30
|
+
* function for all non-primitive Element instances.
|
|
31
|
+
*/
|
|
32
|
+
if ((0, _apidomDatamodel.isPrimitiveElement)(element)) {
|
|
33
|
+
return refractToJSONSchemaElement(element);
|
|
34
|
+
}
|
|
35
|
+
return element;
|
|
36
|
+
};
|
|
37
|
+
exports.maybeRefractToJSONSchemaElement = maybeRefractToJSONSchemaElement;
|