@speclynx/apidom-reference 2.7.0 → 2.9.0
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 +14 -0
- package/README.md +197 -0
- package/dist/apidom-reference.browser.js +221 -83
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +25 -25
- package/src/dereference/strategies/arazzo-1/index.cjs +3 -2
- package/src/dereference/strategies/arazzo-1/index.mjs +3 -2
- package/src/dereference/strategies/arazzo-1/{source-description.cjs → source-descriptions.cjs} +94 -29
- package/src/dereference/strategies/arazzo-1/{source-description.mjs → source-descriptions.mjs} +95 -28
- package/src/parse/parsers/arazzo-json-1/index.cjs +3 -2
- package/src/parse/parsers/arazzo-json-1/index.mjs +3 -2
- package/src/parse/parsers/arazzo-json-1/{source-description.cjs → source-descriptions.cjs} +58 -12
- package/src/parse/parsers/arazzo-json-1/{source-description.mjs → source-descriptions.mjs} +57 -11
- package/src/parse/parsers/arazzo-yaml-1/index.cjs +3 -2
- package/src/parse/parsers/arazzo-yaml-1/index.mjs +3 -2
- 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/types/dereference/strategies/arazzo-1/index.d.ts +1 -0
- package/types/dereference/strategies/arazzo-1/source-descriptions.d.ts +41 -0
- package/types/parse/parsers/arazzo-json-1/index.d.ts +1 -0
- package/types/parse/parsers/arazzo-json-1/source-descriptions.d.ts +44 -0
- package/types/parse/parsers/arazzo-yaml-1/index.d.ts +1 -0
- package/types/parse/parsers/arazzo-yaml-1/source-descriptions.d.ts +6 -0
- package/types/dereference/strategies/arazzo-1/source-description.d.ts +0 -8
- package/types/parse/parsers/arazzo-json-1/source-description.d.ts +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speclynx/apidom-reference",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Advanced algorithms for semantic ApiDOM manipulations like dereferencing or resolution.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -231,29 +231,29 @@
|
|
|
231
231
|
"license": "Apache-2.0",
|
|
232
232
|
"dependencies": {
|
|
233
233
|
"@babel/runtime-corejs3": "^7.28.4",
|
|
234
|
-
"@speclynx/apidom-core": "^2.
|
|
235
|
-
"@speclynx/apidom-datamodel": "^2.
|
|
236
|
-
"@speclynx/apidom-error": "^2.
|
|
237
|
-
"@speclynx/apidom-json-pointer": "^2.
|
|
238
|
-
"@speclynx/apidom-ns-arazzo-1": "^2.
|
|
239
|
-
"@speclynx/apidom-ns-asyncapi-2": "^2.
|
|
240
|
-
"@speclynx/apidom-ns-json-schema-2020-12": "^2.
|
|
241
|
-
"@speclynx/apidom-ns-openapi-2": "^2.
|
|
242
|
-
"@speclynx/apidom-ns-openapi-3-0": "^2.
|
|
243
|
-
"@speclynx/apidom-ns-openapi-3-1": "^2.
|
|
244
|
-
"@speclynx/apidom-parser-adapter-arazzo-json-1": "^2.
|
|
245
|
-
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "^2.
|
|
246
|
-
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "^2.
|
|
247
|
-
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "^2.
|
|
248
|
-
"@speclynx/apidom-parser-adapter-json": "^2.
|
|
249
|
-
"@speclynx/apidom-parser-adapter-openapi-json-2": "^2.
|
|
250
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "^2.
|
|
251
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "^2.
|
|
252
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "^2.
|
|
253
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "^2.
|
|
254
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "^2.
|
|
255
|
-
"@speclynx/apidom-parser-adapter-yaml-1-2": "^2.
|
|
256
|
-
"@speclynx/apidom-traverse": "^2.
|
|
234
|
+
"@speclynx/apidom-core": "^2.9.0",
|
|
235
|
+
"@speclynx/apidom-datamodel": "^2.9.0",
|
|
236
|
+
"@speclynx/apidom-error": "^2.9.0",
|
|
237
|
+
"@speclynx/apidom-json-pointer": "^2.9.0",
|
|
238
|
+
"@speclynx/apidom-ns-arazzo-1": "^2.9.0",
|
|
239
|
+
"@speclynx/apidom-ns-asyncapi-2": "^2.9.0",
|
|
240
|
+
"@speclynx/apidom-ns-json-schema-2020-12": "^2.9.0",
|
|
241
|
+
"@speclynx/apidom-ns-openapi-2": "^2.9.0",
|
|
242
|
+
"@speclynx/apidom-ns-openapi-3-0": "^2.9.0",
|
|
243
|
+
"@speclynx/apidom-ns-openapi-3-1": "^2.9.0",
|
|
244
|
+
"@speclynx/apidom-parser-adapter-arazzo-json-1": "^2.9.0",
|
|
245
|
+
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "^2.9.0",
|
|
246
|
+
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "^2.9.0",
|
|
247
|
+
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "^2.9.0",
|
|
248
|
+
"@speclynx/apidom-parser-adapter-json": "^2.9.0",
|
|
249
|
+
"@speclynx/apidom-parser-adapter-openapi-json-2": "^2.9.0",
|
|
250
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "^2.9.0",
|
|
251
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "^2.9.0",
|
|
252
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "^2.9.0",
|
|
253
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "^2.9.0",
|
|
254
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "^2.9.0",
|
|
255
|
+
"@speclynx/apidom-parser-adapter-yaml-1-2": "^2.9.0",
|
|
256
|
+
"@speclynx/apidom-traverse": "^2.9.0",
|
|
257
257
|
"@swaggerexpert/arazzo-runtime-expression": "^2.0.2",
|
|
258
258
|
"axios": "^1.13.0",
|
|
259
259
|
"minimatch": "^7.4.6",
|
|
@@ -274,5 +274,5 @@
|
|
|
274
274
|
"README.md",
|
|
275
275
|
"CHANGELOG.md"
|
|
276
276
|
],
|
|
277
|
-
"gitHead": "
|
|
277
|
+
"gitHead": "171956df4417a88238134c1854a5ff823aa144d8"
|
|
278
278
|
}
|
|
@@ -12,7 +12,8 @@ var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
|
|
|
12
12
|
var _ReferenceSet = _interopRequireDefault(require("../../../ReferenceSet.cjs"));
|
|
13
13
|
var _visitor = _interopRequireDefault(require("./visitor.cjs"));
|
|
14
14
|
exports.Arazzo1DereferenceVisitor = _visitor.default;
|
|
15
|
-
var
|
|
15
|
+
var _sourceDescriptions = require("./source-descriptions.cjs");
|
|
16
|
+
exports.dereferenceSourceDescriptions = _sourceDescriptions.dereferenceSourceDescriptions;
|
|
16
17
|
var _util = require("./util.cjs");
|
|
17
18
|
exports.resolveSchema$refField = _util.resolveSchema$refField;
|
|
18
19
|
exports.resolveSchema$idField = _util.resolveSchema$idField;
|
|
@@ -83,7 +84,7 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
|
|
|
83
84
|
*/
|
|
84
85
|
const shouldDereferenceSourceDescriptions = options?.dereference?.strategyOpts?.[this.name]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
85
86
|
if (shouldDereferenceSourceDescriptions) {
|
|
86
|
-
const sourceDescriptions = await (0,
|
|
87
|
+
const sourceDescriptions = await (0, _sourceDescriptions.dereferenceSourceDescriptions)(dereferencedElement, reference.uri, options, this.name);
|
|
87
88
|
dereferencedElement.push(...sourceDescriptions);
|
|
88
89
|
}
|
|
89
90
|
|
|
@@ -5,7 +5,7 @@ import DereferenceStrategy from "../DereferenceStrategy.mjs";
|
|
|
5
5
|
import Reference from "../../../Reference.mjs";
|
|
6
6
|
import ReferenceSet from "../../../ReferenceSet.mjs";
|
|
7
7
|
import Arazzo1DereferenceVisitor from "./visitor.mjs";
|
|
8
|
-
import { dereferenceSourceDescriptions } from "./source-
|
|
8
|
+
import { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
|
|
9
9
|
/**
|
|
10
10
|
* @public
|
|
11
11
|
*/
|
|
@@ -71,7 +71,7 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
|
|
|
71
71
|
*/
|
|
72
72
|
const shouldDereferenceSourceDescriptions = options?.dereference?.strategyOpts?.[this.name]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
73
73
|
if (shouldDereferenceSourceDescriptions) {
|
|
74
|
-
const sourceDescriptions = await dereferenceSourceDescriptions(dereferencedElement, reference, options);
|
|
74
|
+
const sourceDescriptions = await dereferenceSourceDescriptions(dereferencedElement, reference.uri, options, this.name);
|
|
75
75
|
dereferencedElement.push(...sourceDescriptions);
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -98,4 +98,5 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
|
|
|
98
98
|
}
|
|
99
99
|
export { Arazzo1DereferenceVisitor };
|
|
100
100
|
export { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement } from "./util.mjs";
|
|
101
|
+
export { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
|
|
101
102
|
export default Arazzo1DereferenceStrategy;
|
package/src/dereference/strategies/arazzo-1/{source-description.cjs → source-descriptions.cjs}
RENAMED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
3
|
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
5
4
|
exports.__esModule = true;
|
|
6
5
|
exports.dereferenceSourceDescriptions = dereferenceSourceDescriptions;
|
|
@@ -12,9 +11,7 @@ var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
|
12
11
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
13
12
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
14
13
|
var _util = require("../../../options/util.cjs");
|
|
15
|
-
var _index =
|
|
16
|
-
// shared key for recursion state (works across JSON/YAML documents)
|
|
17
|
-
const ARAZZO_DEREFERENCE_RECURSION_KEY = 'arazzo-1';
|
|
14
|
+
var _index = _interopRequireWildcard(require("../../index.cjs"));
|
|
18
15
|
/**
|
|
19
16
|
* Dereferences a single source description element.
|
|
20
17
|
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
@@ -39,7 +36,9 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
39
36
|
parseResult.push(annotation);
|
|
40
37
|
return parseResult;
|
|
41
38
|
}
|
|
42
|
-
|
|
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)));
|
|
43
42
|
|
|
44
43
|
// skip if already visited (cycle detection)
|
|
45
44
|
if (ctx.visitedUrls.has(retrievalURI)) {
|
|
@@ -49,20 +48,54 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
49
48
|
return parseResult;
|
|
50
49
|
}
|
|
51
50
|
ctx.visitedUrls.add(retrievalURI);
|
|
51
|
+
|
|
52
|
+
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
53
|
+
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
52
54
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
let sourceDescriptionDereferenced;
|
|
56
|
+
if ((0, _apidomDatamodel.isParseResultElement)(existingParseResult)) {
|
|
57
|
+
// use existing parsed result - just dereference it (no re-fetch/re-parse)
|
|
58
|
+
sourceDescriptionDereferenced = await (0, _index.dereferenceApiDOM)(existingParseResult, (0, _util.merge)(ctx.options, {
|
|
59
|
+
parse: {
|
|
60
|
+
mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
|
|
61
|
+
},
|
|
62
|
+
resolve: {
|
|
63
|
+
baseURI: retrievalURI
|
|
64
|
+
},
|
|
65
|
+
dereference: {
|
|
66
|
+
strategyOpts: {
|
|
67
|
+
// nested documents should dereference all their source descriptions
|
|
68
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
69
|
+
// set at strategy-specific level to override any inherited filters
|
|
70
|
+
[ctx.strategyName]: {
|
|
71
|
+
sourceDescriptions: true,
|
|
72
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
73
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
} else {
|
|
79
|
+
// no existing parse result - fetch, parse, and dereference
|
|
80
|
+
sourceDescriptionDereferenced = await (0, _index.default)(retrievalURI, (0, _util.merge)(ctx.options, {
|
|
81
|
+
parse: {
|
|
82
|
+
mediaType: 'text/plain' // allow parser plugin detection
|
|
83
|
+
},
|
|
84
|
+
dereference: {
|
|
85
|
+
strategyOpts: {
|
|
86
|
+
// nested documents should dereference all their source descriptions
|
|
87
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
88
|
+
// set at strategy-specific level to override any inherited filters
|
|
89
|
+
[ctx.strategyName]: {
|
|
90
|
+
sourceDescriptions: true,
|
|
91
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
92
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
93
|
+
}
|
|
62
94
|
}
|
|
63
95
|
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
66
99
|
// merge dereferenced result into our parse result
|
|
67
100
|
for (const item of sourceDescriptionDereferenced) {
|
|
68
101
|
parseResult.push(item);
|
|
@@ -107,11 +140,45 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
107
140
|
|
|
108
141
|
/**
|
|
109
142
|
* Dereferences source descriptions from an Arazzo document.
|
|
143
|
+
*
|
|
144
|
+
* Each source description result is attached to its corresponding
|
|
145
|
+
* SourceDescriptionElement's meta as 'parseResult' for easy access,
|
|
146
|
+
* regardless of success or failure. On failure, the ParseResultElement
|
|
147
|
+
* contains annotations explaining what went wrong.
|
|
148
|
+
*
|
|
149
|
+
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
150
|
+
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
151
|
+
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
152
|
+
* in `dereference.strategyOpts` to filter which source descriptions to process.
|
|
153
|
+
* @param strategyName - Strategy name for options lookup (defaults to 'arazzo-1')
|
|
154
|
+
* @returns Array of ParseResultElements. Returns one ParseResultElement per source description
|
|
155
|
+
* (each with class 'source-description' and name/type metadata).
|
|
156
|
+
* May return early with a single-element array containing a warning annotation when:
|
|
157
|
+
* - The API is not an Arazzo specification
|
|
158
|
+
* - The sourceDescriptions field is missing or not an array
|
|
159
|
+
* - Maximum dereference depth is exceeded (error annotation)
|
|
160
|
+
* Returns an empty array when no source description names match the filter.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* // Dereference all source descriptions
|
|
165
|
+
* await dereferenceSourceDescriptions(parseResult, uri, options);
|
|
166
|
+
*
|
|
167
|
+
* // Filter by name
|
|
168
|
+
* await dereferenceSourceDescriptions(parseResult, uri, mergeOptions(options, {
|
|
169
|
+
* dereference: { strategyOpts: { sourceDescriptions: ['petStore'] } },
|
|
170
|
+
* }));
|
|
171
|
+
*
|
|
172
|
+
* // Access dereferenced document from source description element
|
|
173
|
+
* const sourceDesc = parseResult.api.sourceDescriptions.get(0);
|
|
174
|
+
* const dereferencedDoc = sourceDesc.meta.get('parseResult');
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
110
177
|
* @public
|
|
111
178
|
*/
|
|
112
|
-
async function dereferenceSourceDescriptions(parseResult,
|
|
179
|
+
async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalURI, options, strategyName = 'arazzo-1') {
|
|
180
|
+
const baseURI = url.sanitize(url.stripHash(parseResultRetrievalURI));
|
|
113
181
|
const results = [];
|
|
114
|
-
const strategyName = 'arazzo-1';
|
|
115
182
|
|
|
116
183
|
// get API from dereferenced parse result
|
|
117
184
|
const {
|
|
@@ -136,34 +203,30 @@ async function dereferenceSourceDescriptions(parseResult, reference, options) {
|
|
|
136
203
|
// user config: strategy-specific options take precedence over global strategyOpts
|
|
137
204
|
const maxDepth = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptionsMaxDepth ?? options?.dereference?.strategyOpts?.sourceDescriptionsMaxDepth ?? +Infinity;
|
|
138
205
|
|
|
139
|
-
// recursion state comes from
|
|
140
|
-
const sharedOpts = options?.dereference?.strategyOpts?.[
|
|
206
|
+
// recursion state comes from strategy-specific options
|
|
207
|
+
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
141
208
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
142
209
|
const visitedUrls = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
|
|
143
210
|
|
|
144
211
|
// add current file to visited URLs to prevent cycles
|
|
145
|
-
visitedUrls.add(
|
|
212
|
+
visitedUrls.add(baseURI);
|
|
146
213
|
if (currentDepth >= maxDepth) {
|
|
147
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${
|
|
214
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${baseURI}"`);
|
|
148
215
|
annotation.classes.push('error');
|
|
149
216
|
const parseResult = new _apidomDatamodel.ParseResultElement([annotation]);
|
|
150
217
|
parseResult.classes.push('source-description');
|
|
151
218
|
return [parseResult];
|
|
152
219
|
}
|
|
153
220
|
const ctx = {
|
|
154
|
-
baseURI
|
|
221
|
+
baseURI,
|
|
155
222
|
options,
|
|
223
|
+
strategyName,
|
|
156
224
|
currentDepth,
|
|
157
225
|
visitedUrls
|
|
158
226
|
};
|
|
159
227
|
|
|
160
|
-
// determine which source descriptions to dereference
|
|
228
|
+
// determine which source descriptions to dereference (array filters by name)
|
|
161
229
|
const sourceDescriptionsOption = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
162
|
-
|
|
163
|
-
// handle false or other falsy values - no source descriptions should be dereferenced
|
|
164
|
-
if (!sourceDescriptionsOption) {
|
|
165
|
-
return results;
|
|
166
|
-
}
|
|
167
230
|
const sourceDescriptions = Array.isArray(sourceDescriptionsOption) ? api.sourceDescriptions.filter(sd => {
|
|
168
231
|
if (!(0, _apidomNsArazzo.isSourceDescriptionElement)(sd)) return false;
|
|
169
232
|
const name = (0, _apidomCore.toValue)(sd.name);
|
|
@@ -173,6 +236,8 @@ async function dereferenceSourceDescriptions(parseResult, reference, options) {
|
|
|
173
236
|
// process sequentially to ensure proper cycle detection with shared visitedUrls
|
|
174
237
|
for (const sourceDescription of sourceDescriptions) {
|
|
175
238
|
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
239
|
+
// always attach result (even on failure - contains annotations)
|
|
240
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult);
|
|
176
241
|
results.push(sourceDescriptionDereferenceResult);
|
|
177
242
|
}
|
|
178
243
|
return results;
|
package/src/dereference/strategies/arazzo-1/{source-description.mjs → source-descriptions.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
1
|
+
import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement, isParseResultElement, cloneDeep } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { isArazzoSpecification1Element, isSourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
3
3
|
import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
|
|
4
4
|
import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
|
|
@@ -6,8 +6,7 @@ import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
|
|
|
6
6
|
import { toValue } from '@speclynx/apidom-core';
|
|
7
7
|
import * as url from "../../../util/url.mjs";
|
|
8
8
|
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
9
|
-
import dereference from "../../index.mjs";
|
|
10
|
-
const ARAZZO_DEREFERENCE_RECURSION_KEY = 'arazzo-1';
|
|
9
|
+
import dereference, { dereferenceApiDOM } from "../../index.mjs";
|
|
11
10
|
/**
|
|
12
11
|
* Dereferences a single source description element.
|
|
13
12
|
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
@@ -32,7 +31,9 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
32
31
|
parseResult.push(annotation);
|
|
33
32
|
return parseResult;
|
|
34
33
|
}
|
|
35
|
-
|
|
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)));
|
|
36
37
|
|
|
37
38
|
// skip if already visited (cycle detection)
|
|
38
39
|
if (ctx.visitedUrls.has(retrievalURI)) {
|
|
@@ -42,20 +43,54 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
42
43
|
return parseResult;
|
|
43
44
|
}
|
|
44
45
|
ctx.visitedUrls.add(retrievalURI);
|
|
46
|
+
|
|
47
|
+
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
48
|
+
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
45
49
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
let sourceDescriptionDereferenced;
|
|
51
|
+
if (isParseResultElement(existingParseResult)) {
|
|
52
|
+
// use existing parsed result - just dereference it (no re-fetch/re-parse)
|
|
53
|
+
sourceDescriptionDereferenced = await dereferenceApiDOM(existingParseResult, mergeOptions(ctx.options, {
|
|
54
|
+
parse: {
|
|
55
|
+
mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
|
|
56
|
+
},
|
|
57
|
+
resolve: {
|
|
58
|
+
baseURI: retrievalURI
|
|
59
|
+
},
|
|
60
|
+
dereference: {
|
|
61
|
+
strategyOpts: {
|
|
62
|
+
// nested documents should dereference all their source descriptions
|
|
63
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
64
|
+
// set at strategy-specific level to override any inherited filters
|
|
65
|
+
[ctx.strategyName]: {
|
|
66
|
+
sourceDescriptions: true,
|
|
67
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
68
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
} else {
|
|
74
|
+
// no existing parse result - fetch, parse, and dereference
|
|
75
|
+
sourceDescriptionDereferenced = await dereference(retrievalURI, mergeOptions(ctx.options, {
|
|
76
|
+
parse: {
|
|
77
|
+
mediaType: 'text/plain' // allow parser plugin detection
|
|
78
|
+
},
|
|
79
|
+
dereference: {
|
|
80
|
+
strategyOpts: {
|
|
81
|
+
// nested documents should dereference all their source descriptions
|
|
82
|
+
// (parent's name filter doesn't apply to nested documents)
|
|
83
|
+
// set at strategy-specific level to override any inherited filters
|
|
84
|
+
[ctx.strategyName]: {
|
|
85
|
+
sourceDescriptions: true,
|
|
86
|
+
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
87
|
+
sourceDescriptionsVisitedUrls: ctx.visitedUrls
|
|
88
|
+
}
|
|
55
89
|
}
|
|
56
90
|
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
59
94
|
// merge dereferenced result into our parse result
|
|
60
95
|
for (const item of sourceDescriptionDereferenced) {
|
|
61
96
|
parseResult.push(item);
|
|
@@ -100,11 +135,45 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
100
135
|
|
|
101
136
|
/**
|
|
102
137
|
* Dereferences source descriptions from an Arazzo document.
|
|
138
|
+
*
|
|
139
|
+
* Each source description result is attached to its corresponding
|
|
140
|
+
* SourceDescriptionElement's meta as 'parseResult' for easy access,
|
|
141
|
+
* regardless of success or failure. On failure, the ParseResultElement
|
|
142
|
+
* contains annotations explaining what went wrong.
|
|
143
|
+
*
|
|
144
|
+
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
145
|
+
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
146
|
+
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
147
|
+
* in `dereference.strategyOpts` to filter which source descriptions to process.
|
|
148
|
+
* @param strategyName - Strategy name for options lookup (defaults to 'arazzo-1')
|
|
149
|
+
* @returns Array of ParseResultElements. Returns one ParseResultElement per source description
|
|
150
|
+
* (each with class 'source-description' and name/type metadata).
|
|
151
|
+
* May return early with a single-element array containing a warning annotation when:
|
|
152
|
+
* - The API is not an Arazzo specification
|
|
153
|
+
* - The sourceDescriptions field is missing or not an array
|
|
154
|
+
* - Maximum dereference depth is exceeded (error annotation)
|
|
155
|
+
* Returns an empty array when no source description names match the filter.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* // Dereference all source descriptions
|
|
160
|
+
* await dereferenceSourceDescriptions(parseResult, uri, options);
|
|
161
|
+
*
|
|
162
|
+
* // Filter by name
|
|
163
|
+
* await dereferenceSourceDescriptions(parseResult, uri, mergeOptions(options, {
|
|
164
|
+
* dereference: { strategyOpts: { sourceDescriptions: ['petStore'] } },
|
|
165
|
+
* }));
|
|
166
|
+
*
|
|
167
|
+
* // Access dereferenced document from source description element
|
|
168
|
+
* const sourceDesc = parseResult.api.sourceDescriptions.get(0);
|
|
169
|
+
* const dereferencedDoc = sourceDesc.meta.get('parseResult');
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
103
172
|
* @public
|
|
104
173
|
*/
|
|
105
|
-
export async function dereferenceSourceDescriptions(parseResult,
|
|
174
|
+
export async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalURI, options, strategyName = 'arazzo-1') {
|
|
175
|
+
const baseURI = url.sanitize(url.stripHash(parseResultRetrievalURI));
|
|
106
176
|
const results = [];
|
|
107
|
-
const strategyName = 'arazzo-1';
|
|
108
177
|
|
|
109
178
|
// get API from dereferenced parse result
|
|
110
179
|
const {
|
|
@@ -129,34 +198,30 @@ export async function dereferenceSourceDescriptions(parseResult, reference, opti
|
|
|
129
198
|
// user config: strategy-specific options take precedence over global strategyOpts
|
|
130
199
|
const maxDepth = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptionsMaxDepth ?? options?.dereference?.strategyOpts?.sourceDescriptionsMaxDepth ?? +Infinity;
|
|
131
200
|
|
|
132
|
-
// recursion state comes from
|
|
133
|
-
const sharedOpts = options?.dereference?.strategyOpts?.[
|
|
201
|
+
// recursion state comes from strategy-specific options
|
|
202
|
+
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
134
203
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
135
204
|
const visitedUrls = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
|
|
136
205
|
|
|
137
206
|
// add current file to visited URLs to prevent cycles
|
|
138
|
-
visitedUrls.add(
|
|
207
|
+
visitedUrls.add(baseURI);
|
|
139
208
|
if (currentDepth >= maxDepth) {
|
|
140
|
-
const annotation = new AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${
|
|
209
|
+
const annotation = new AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${baseURI}"`);
|
|
141
210
|
annotation.classes.push('error');
|
|
142
211
|
const parseResult = new ParseResultElement([annotation]);
|
|
143
212
|
parseResult.classes.push('source-description');
|
|
144
213
|
return [parseResult];
|
|
145
214
|
}
|
|
146
215
|
const ctx = {
|
|
147
|
-
baseURI
|
|
216
|
+
baseURI,
|
|
148
217
|
options,
|
|
218
|
+
strategyName,
|
|
149
219
|
currentDepth,
|
|
150
220
|
visitedUrls
|
|
151
221
|
};
|
|
152
222
|
|
|
153
|
-
// determine which source descriptions to dereference
|
|
223
|
+
// determine which source descriptions to dereference (array filters by name)
|
|
154
224
|
const sourceDescriptionsOption = options?.dereference?.strategyOpts?.[strategyName]?.sourceDescriptions ?? options?.dereference?.strategyOpts?.sourceDescriptions;
|
|
155
|
-
|
|
156
|
-
// handle false or other falsy values - no source descriptions should be dereferenced
|
|
157
|
-
if (!sourceDescriptionsOption) {
|
|
158
|
-
return results;
|
|
159
|
-
}
|
|
160
225
|
const sourceDescriptions = Array.isArray(sourceDescriptionsOption) ? api.sourceDescriptions.filter(sd => {
|
|
161
226
|
if (!isSourceDescriptionElement(sd)) return false;
|
|
162
227
|
const name = toValue(sd.name);
|
|
@@ -166,6 +231,8 @@ export async function dereferenceSourceDescriptions(parseResult, reference, opti
|
|
|
166
231
|
// process sequentially to ensure proper cycle detection with shared visitedUrls
|
|
167
232
|
for (const sourceDescription of sourceDescriptions) {
|
|
168
233
|
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
234
|
+
// always attach result (even on failure - contains annotations)
|
|
235
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult);
|
|
169
236
|
results.push(sourceDescriptionDereferenceResult);
|
|
170
237
|
}
|
|
171
238
|
return results;
|
|
@@ -7,7 +7,8 @@ var _ramda = require("ramda");
|
|
|
7
7
|
var _apidomParserAdapterArazzoJson = require("@speclynx/apidom-parser-adapter-arazzo-json-1");
|
|
8
8
|
var _ParserError = _interopRequireDefault(require("../../../errors/ParserError.cjs"));
|
|
9
9
|
var _Parser = _interopRequireDefault(require("../Parser.cjs"));
|
|
10
|
-
var
|
|
10
|
+
var _sourceDescriptions = require("./source-descriptions.cjs");
|
|
11
|
+
exports.parseSourceDescriptions = _sourceDescriptions.parseSourceDescriptions;
|
|
11
12
|
/**
|
|
12
13
|
* @public
|
|
13
14
|
*/
|
|
@@ -47,7 +48,7 @@ class ArazzoJSON1Parser extends _Parser.default {
|
|
|
47
48
|
const parseResult = await (0, _apidomParserAdapterArazzoJson.parse)(source, parserOpts);
|
|
48
49
|
const shouldParseSourceDescriptions = options?.parse?.parserOpts?.[this.name]?.sourceDescriptions ?? options?.parse?.parserOpts?.sourceDescriptions;
|
|
49
50
|
if (shouldParseSourceDescriptions) {
|
|
50
|
-
const sourceDescriptions = await (0,
|
|
51
|
+
const sourceDescriptions = await (0, _sourceDescriptions.parseSourceDescriptions)(parseResult, file.uri, options, this.name);
|
|
51
52
|
parseResult.push(...sourceDescriptions);
|
|
52
53
|
}
|
|
53
54
|
return parseResult;
|
|
@@ -2,7 +2,7 @@ import { pick } from 'ramda';
|
|
|
2
2
|
import { parse, mediaTypes as ArazzoJSON1MediaTypes, detect } from '@speclynx/apidom-parser-adapter-arazzo-json-1';
|
|
3
3
|
import ParserError from "../../../errors/ParserError.mjs";
|
|
4
4
|
import Parser from "../Parser.mjs";
|
|
5
|
-
import { parseSourceDescriptions } from "./source-
|
|
5
|
+
import { parseSourceDescriptions } from "./source-descriptions.mjs";
|
|
6
6
|
/**
|
|
7
7
|
* @public
|
|
8
8
|
*/
|
|
@@ -41,7 +41,7 @@ class ArazzoJSON1Parser extends Parser {
|
|
|
41
41
|
const parseResult = await parse(source, parserOpts);
|
|
42
42
|
const shouldParseSourceDescriptions = options?.parse?.parserOpts?.[this.name]?.sourceDescriptions ?? options?.parse?.parserOpts?.sourceDescriptions;
|
|
43
43
|
if (shouldParseSourceDescriptions) {
|
|
44
|
-
const sourceDescriptions = await parseSourceDescriptions(
|
|
44
|
+
const sourceDescriptions = await parseSourceDescriptions(parseResult, file.uri, options, this.name);
|
|
45
45
|
parseResult.push(...sourceDescriptions);
|
|
46
46
|
}
|
|
47
47
|
return parseResult;
|
|
@@ -52,4 +52,5 @@ class ArazzoJSON1Parser extends Parser {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
export { parseSourceDescriptions } from "./source-descriptions.mjs";
|
|
55
56
|
export default ArazzoJSON1Parser;
|