@speclynx/apidom-reference 5.2.1 → 5.2.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 +14 -0
- package/README.md +18 -0
- package/dist/apidom-reference.browser.js +1095 -586
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +30 -30
- package/src/dereference/strategies/arazzo-1/source-descriptions.cjs +40 -43
- package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +41 -44
- package/src/dereference/strategies/arazzo-1/util.cjs +47 -2
- package/src/dereference/strategies/arazzo-1/util.mjs +43 -1
- package/src/parse/parsers/arazzo-json-1/source-descriptions.cjs +38 -42
- package/src/parse/parsers/arazzo-json-1/source-descriptions.mjs +39 -43
- package/types/dereference/strategies/arazzo-1/source-descriptions.d.ts +7 -0
- package/types/dereference/strategies/arazzo-1/util.d.ts +12 -1
- package/types/parse/parsers/arazzo-json-1/source-descriptions.d.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speclynx/apidom-reference",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.4",
|
|
4
4
|
"description": "Advanced algorithms for semantic ApiDOM manipulations like dereferencing or resolution.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apidom",
|
|
@@ -299,34 +299,34 @@
|
|
|
299
299
|
"license": "Apache-2.0",
|
|
300
300
|
"dependencies": {
|
|
301
301
|
"@babel/runtime-corejs3": "^8.0.0",
|
|
302
|
-
"@speclynx/apidom-core": "5.2.
|
|
303
|
-
"@speclynx/apidom-datamodel": "5.2.
|
|
304
|
-
"@speclynx/apidom-error": "5.2.
|
|
305
|
-
"@speclynx/apidom-json-pointer": "5.2.
|
|
306
|
-
"@speclynx/apidom-ns-arazzo-1": "5.2.
|
|
307
|
-
"@speclynx/apidom-ns-asyncapi-2": "5.2.
|
|
308
|
-
"@speclynx/apidom-ns-json-schema-2020-12": "5.2.
|
|
309
|
-
"@speclynx/apidom-ns-openapi-2": "5.2.
|
|
310
|
-
"@speclynx/apidom-ns-openapi-3-0": "5.2.
|
|
311
|
-
"@speclynx/apidom-ns-openapi-3-1": "5.2.
|
|
312
|
-
"@speclynx/apidom-ns-overlay-1": "5.2.
|
|
313
|
-
"@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.
|
|
314
|
-
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.
|
|
315
|
-
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.
|
|
316
|
-
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.
|
|
317
|
-
"@speclynx/apidom-parser-adapter-json": "5.2.
|
|
318
|
-
"@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.
|
|
319
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.
|
|
320
|
-
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.
|
|
321
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.
|
|
322
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.
|
|
323
|
-
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.
|
|
324
|
-
"@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.
|
|
325
|
-
"@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.
|
|
326
|
-
"@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.
|
|
327
|
-
"@speclynx/apidom-traverse": "5.2.
|
|
328
|
-
"@swaggerexpert/arazzo-runtime-expression": "^3.
|
|
329
|
-
"axios": "^1.
|
|
302
|
+
"@speclynx/apidom-core": "5.2.4",
|
|
303
|
+
"@speclynx/apidom-datamodel": "5.2.4",
|
|
304
|
+
"@speclynx/apidom-error": "5.2.4",
|
|
305
|
+
"@speclynx/apidom-json-pointer": "5.2.4",
|
|
306
|
+
"@speclynx/apidom-ns-arazzo-1": "5.2.4",
|
|
307
|
+
"@speclynx/apidom-ns-asyncapi-2": "5.2.4",
|
|
308
|
+
"@speclynx/apidom-ns-json-schema-2020-12": "5.2.4",
|
|
309
|
+
"@speclynx/apidom-ns-openapi-2": "5.2.4",
|
|
310
|
+
"@speclynx/apidom-ns-openapi-3-0": "5.2.4",
|
|
311
|
+
"@speclynx/apidom-ns-openapi-3-1": "5.2.4",
|
|
312
|
+
"@speclynx/apidom-ns-overlay-1": "5.2.4",
|
|
313
|
+
"@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.4",
|
|
314
|
+
"@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.4",
|
|
315
|
+
"@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.4",
|
|
316
|
+
"@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.4",
|
|
317
|
+
"@speclynx/apidom-parser-adapter-json": "5.2.4",
|
|
318
|
+
"@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.4",
|
|
319
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.4",
|
|
320
|
+
"@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.4",
|
|
321
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.4",
|
|
322
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.4",
|
|
323
|
+
"@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.4",
|
|
324
|
+
"@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.4",
|
|
325
|
+
"@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.4",
|
|
326
|
+
"@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.4",
|
|
327
|
+
"@speclynx/apidom-traverse": "5.2.4",
|
|
328
|
+
"@swaggerexpert/arazzo-runtime-expression": "^3.2.0",
|
|
329
|
+
"axios": "^1.20.0",
|
|
330
330
|
"picomatch": "^4.0.7",
|
|
331
331
|
"process": "^0.11.10",
|
|
332
332
|
"ramda": "~0.32.0",
|
|
@@ -346,5 +346,5 @@
|
|
|
346
346
|
"README.md",
|
|
347
347
|
"CHANGELOG.md"
|
|
348
348
|
],
|
|
349
|
-
"gitHead": "
|
|
349
|
+
"gitHead": "5b467dbfe42b1989fe64b4a83c226274de24d783"
|
|
350
350
|
}
|
|
@@ -7,9 +7,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
exports.dereferenceSourceDescriptions = dereferenceSourceDescriptions;
|
|
8
8
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
9
9
|
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
10
|
-
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
11
|
-
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
12
|
-
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
13
10
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
14
11
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
15
12
|
var _util = require("../../../options/util.cjs");
|
|
@@ -44,14 +41,25 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
44
41
|
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
45
42
|
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
46
43
|
|
|
47
|
-
// skip if
|
|
48
|
-
if (ctx.
|
|
44
|
+
// skip if the document is still being dereferenced higher up the chain (cycle detection)
|
|
45
|
+
if (ctx.ancestors.includes(retrievalURI)) {
|
|
49
46
|
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
50
47
|
annotation.classes.push('warning');
|
|
51
48
|
parseResult.push(annotation);
|
|
52
49
|
return parseResult;
|
|
53
50
|
}
|
|
54
|
-
|
|
51
|
+
|
|
52
|
+
// a document reached again through a different path is a shared dependency, not a cycle;
|
|
53
|
+
// point at the result where it was dereferenced instead of dereferencing it again
|
|
54
|
+
const sharedDereferenceResult = ctx.dereferenced.get(retrievalURI);
|
|
55
|
+
if (sharedDereferenceResult !== undefined) {
|
|
56
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" has already been dereferenced. Reusing existing dereference result`);
|
|
57
|
+
annotation.classes.push('info');
|
|
58
|
+
parseResult.push(annotation);
|
|
59
|
+
parseResult.meta.set('parseResult', sharedDereferenceResult);
|
|
60
|
+
(0, _util2.validateSourceDescriptionAPI)(parseResult, sourceDescription, sharedDereferenceResult.api, retrievalURI);
|
|
61
|
+
return parseResult;
|
|
62
|
+
}
|
|
55
63
|
|
|
56
64
|
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
57
65
|
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
@@ -74,7 +82,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
74
82
|
[ctx.strategyName]: {
|
|
75
83
|
sourceDescriptions: true,
|
|
76
84
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
77
|
-
|
|
85
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
86
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
89
|
}
|
|
@@ -93,7 +102,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
93
102
|
[ctx.strategyName]: {
|
|
94
103
|
sourceDescriptions: true,
|
|
95
104
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
96
|
-
|
|
105
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
106
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
}
|
|
@@ -113,32 +123,14 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
113
123
|
return parseResult;
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
//
|
|
126
|
+
// register dereferenced document for later references to it
|
|
117
127
|
const {
|
|
118
128
|
api: sourceDescriptionAPI
|
|
119
129
|
} = parseResult;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
if (!isOpenApi && !isArazzo) {
|
|
123
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
124
|
-
annotation.classes.push('warning');
|
|
125
|
-
parseResult.push(annotation);
|
|
126
|
-
return parseResult;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// validate declared type matches actual dereferenced type
|
|
130
|
-
const declaredType = (0, _apidomCore.toValue)(sourceDescription.type);
|
|
131
|
-
if (typeof declaredType === 'string') {
|
|
132
|
-
if (declaredType === 'openapi' && !isOpenApi) {
|
|
133
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but dereferenced as Arazzo document`);
|
|
134
|
-
annotation.classes.push('warning');
|
|
135
|
-
parseResult.push(annotation);
|
|
136
|
-
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
137
|
-
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but dereferenced as OpenAPI document`);
|
|
138
|
-
annotation.classes.push('warning');
|
|
139
|
-
parseResult.push(annotation);
|
|
140
|
-
}
|
|
130
|
+
for (const documentURI of (0, _util2.arazzoDocumentURIs)(retrievalURI, sourceDescriptionAPI)) {
|
|
131
|
+
ctx.dereferenced.set(documentURI, parseResult);
|
|
141
132
|
}
|
|
133
|
+
(0, _util2.validateSourceDescriptionAPI)(parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI);
|
|
142
134
|
return parseResult;
|
|
143
135
|
}
|
|
144
136
|
|
|
@@ -150,6 +142,13 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
150
142
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
151
143
|
* contains annotations explaining what went wrong.
|
|
152
144
|
*
|
|
145
|
+
* A document referenced from more than one place is dereferenced only once.
|
|
146
|
+
* Every later source description referencing it gets its own result carrying
|
|
147
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
148
|
+
* the document was dereferenced; that result is also what gets attached to the
|
|
149
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
150
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
151
|
+
*
|
|
153
152
|
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
154
153
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
155
154
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -213,14 +212,9 @@ async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalUR
|
|
|
213
212
|
// recursion state comes from strategy-specific options
|
|
214
213
|
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
215
214
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const baseURI = (0, _util2.resolveArazzo$selfField)(retrievalURI, api);
|
|
220
|
-
|
|
221
|
-
// add current file to visited URLs to prevent cycles (by retrieval URI and by identity)
|
|
222
|
-
visitedUrls.add(retrievalURI);
|
|
223
|
-
visitedUrls.add(baseURI);
|
|
215
|
+
const dereferenced = sharedOpts.sourceDescriptionsDereferenced ?? new Map();
|
|
216
|
+
// current file is an ancestor of everything dereferenced below
|
|
217
|
+
const ancestors = [...(sharedOpts.sourceDescriptionsAncestors ?? []), ...(0, _util2.arazzoDocumentURIs)(retrievalURI, api)];
|
|
224
218
|
if (currentDepth >= maxDepth) {
|
|
225
219
|
const annotation = new _apidomDatamodel.AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${retrievalURI}"`);
|
|
226
220
|
annotation.classes.push('error');
|
|
@@ -229,11 +223,13 @@ async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalUR
|
|
|
229
223
|
return [parseResult];
|
|
230
224
|
}
|
|
231
225
|
const ctx = {
|
|
232
|
-
|
|
226
|
+
// base URI for resolving source description URLs honors Arazzo `$self` field
|
|
227
|
+
baseURI: (0, _util2.resolveArazzo$selfField)(retrievalURI, api),
|
|
233
228
|
options,
|
|
234
229
|
strategyName,
|
|
235
230
|
currentDepth,
|
|
236
|
-
|
|
231
|
+
ancestors,
|
|
232
|
+
dereferenced
|
|
237
233
|
};
|
|
238
234
|
|
|
239
235
|
// determine which source descriptions to dereference (array filters by name)
|
|
@@ -244,11 +240,12 @@ async function dereferenceSourceDescriptions(parseResult, parseResultRetrievalUR
|
|
|
244
240
|
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
245
241
|
}) : api.sourceDescriptions;
|
|
246
242
|
|
|
247
|
-
// process sequentially to ensure proper cycle detection with shared
|
|
243
|
+
// process sequentially to ensure proper cycle detection with shared state
|
|
248
244
|
for (const sourceDescription of sourceDescriptions) {
|
|
249
245
|
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
250
|
-
// always attach result (even on failure - contains annotations)
|
|
251
|
-
|
|
246
|
+
// always attach result (even on failure - contains annotations);
|
|
247
|
+
// a shared document attaches the result where it was dereferenced
|
|
248
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult.meta.get('parseResult') ?? sourceDescriptionDereferenceResult);
|
|
252
249
|
results.push(sourceDescriptionDereferenceResult);
|
|
253
250
|
}
|
|
254
251
|
return results;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement, isParseResultElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { isArazzoSpecification1Element, isSourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
3
|
-
import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
|
|
4
|
-
import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
|
|
5
|
-
import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
|
|
6
3
|
import { toValue } from '@speclynx/apidom-core';
|
|
7
4
|
import * as url from "../../../util/url.mjs";
|
|
8
5
|
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
9
6
|
import dereference, { dereferenceApiDOM } from "../../index.mjs";
|
|
10
|
-
import { resolveArazzo$selfField } from "./util.mjs";
|
|
7
|
+
import { arazzoDocumentURIs, resolveArazzo$selfField, validateSourceDescriptionAPI } from "./util.mjs";
|
|
11
8
|
/**
|
|
12
9
|
* Dereferences a single source description element.
|
|
13
10
|
* Returns ParseResultElement on success, or with annotation if skipped.
|
|
@@ -37,14 +34,25 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
37
34
|
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
38
35
|
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
39
36
|
|
|
40
|
-
// skip if
|
|
41
|
-
if (ctx.
|
|
37
|
+
// skip if the document is still being dereferenced higher up the chain (cycle detection)
|
|
38
|
+
if (ctx.ancestors.includes(retrievalURI)) {
|
|
42
39
|
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
43
40
|
annotation.classes.push('warning');
|
|
44
41
|
parseResult.push(annotation);
|
|
45
42
|
return parseResult;
|
|
46
43
|
}
|
|
47
|
-
|
|
44
|
+
|
|
45
|
+
// a document reached again through a different path is a shared dependency, not a cycle;
|
|
46
|
+
// point at the result where it was dereferenced instead of dereferencing it again
|
|
47
|
+
const sharedDereferenceResult = ctx.dereferenced.get(retrievalURI);
|
|
48
|
+
if (sharedDereferenceResult !== undefined) {
|
|
49
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been dereferenced. Reusing existing dereference result`);
|
|
50
|
+
annotation.classes.push('info');
|
|
51
|
+
parseResult.push(annotation);
|
|
52
|
+
parseResult.meta.set('parseResult', sharedDereferenceResult);
|
|
53
|
+
validateSourceDescriptionAPI(parseResult, sourceDescription, sharedDereferenceResult.api, retrievalURI);
|
|
54
|
+
return parseResult;
|
|
55
|
+
}
|
|
48
56
|
|
|
49
57
|
// check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
|
|
50
58
|
const existingParseResult = sourceDescription.meta.get('parseResult');
|
|
@@ -67,7 +75,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
67
75
|
[ctx.strategyName]: {
|
|
68
76
|
sourceDescriptions: true,
|
|
69
77
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
70
|
-
|
|
78
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
79
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
}
|
|
@@ -86,7 +95,8 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
86
95
|
[ctx.strategyName]: {
|
|
87
96
|
sourceDescriptions: true,
|
|
88
97
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
89
|
-
|
|
98
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
99
|
+
sourceDescriptionsDereferenced: ctx.dereferenced
|
|
90
100
|
}
|
|
91
101
|
}
|
|
92
102
|
}
|
|
@@ -106,32 +116,14 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
106
116
|
return parseResult;
|
|
107
117
|
}
|
|
108
118
|
|
|
109
|
-
//
|
|
119
|
+
// register dereferenced document for later references to it
|
|
110
120
|
const {
|
|
111
121
|
api: sourceDescriptionAPI
|
|
112
122
|
} = parseResult;
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
if (!isOpenApi && !isArazzo) {
|
|
116
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
117
|
-
annotation.classes.push('warning');
|
|
118
|
-
parseResult.push(annotation);
|
|
119
|
-
return parseResult;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// validate declared type matches actual dereferenced type
|
|
123
|
-
const declaredType = toValue(sourceDescription.type);
|
|
124
|
-
if (typeof declaredType === 'string') {
|
|
125
|
-
if (declaredType === 'openapi' && !isOpenApi) {
|
|
126
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but dereferenced as Arazzo document`);
|
|
127
|
-
annotation.classes.push('warning');
|
|
128
|
-
parseResult.push(annotation);
|
|
129
|
-
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
130
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but dereferenced as OpenAPI document`);
|
|
131
|
-
annotation.classes.push('warning');
|
|
132
|
-
parseResult.push(annotation);
|
|
133
|
-
}
|
|
123
|
+
for (const documentURI of arazzoDocumentURIs(retrievalURI, sourceDescriptionAPI)) {
|
|
124
|
+
ctx.dereferenced.set(documentURI, parseResult);
|
|
134
125
|
}
|
|
126
|
+
validateSourceDescriptionAPI(parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI);
|
|
135
127
|
return parseResult;
|
|
136
128
|
}
|
|
137
129
|
|
|
@@ -143,6 +135,13 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
|
|
|
143
135
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
144
136
|
* contains annotations explaining what went wrong.
|
|
145
137
|
*
|
|
138
|
+
* A document referenced from more than one place is dereferenced only once.
|
|
139
|
+
* Every later source description referencing it gets its own result carrying
|
|
140
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
141
|
+
* the document was dereferenced; that result is also what gets attached to the
|
|
142
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
143
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
144
|
+
*
|
|
146
145
|
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
147
146
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
148
147
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -206,14 +205,9 @@ export async function dereferenceSourceDescriptions(parseResult, parseResultRetr
|
|
|
206
205
|
// recursion state comes from strategy-specific options
|
|
207
206
|
const sharedOpts = options?.dereference?.strategyOpts?.[strategyName] ?? {};
|
|
208
207
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const baseURI = resolveArazzo$selfField(retrievalURI, api);
|
|
213
|
-
|
|
214
|
-
// add current file to visited URLs to prevent cycles (by retrieval URI and by identity)
|
|
215
|
-
visitedUrls.add(retrievalURI);
|
|
216
|
-
visitedUrls.add(baseURI);
|
|
208
|
+
const dereferenced = sharedOpts.sourceDescriptionsDereferenced ?? new Map();
|
|
209
|
+
// current file is an ancestor of everything dereferenced below
|
|
210
|
+
const ancestors = [...(sharedOpts.sourceDescriptionsAncestors ?? []), ...arazzoDocumentURIs(retrievalURI, api)];
|
|
217
211
|
if (currentDepth >= maxDepth) {
|
|
218
212
|
const annotation = new AnnotationElement(`Maximum dereference depth of ${maxDepth} has been exceeded by file "${retrievalURI}"`);
|
|
219
213
|
annotation.classes.push('error');
|
|
@@ -222,11 +216,13 @@ export async function dereferenceSourceDescriptions(parseResult, parseResultRetr
|
|
|
222
216
|
return [parseResult];
|
|
223
217
|
}
|
|
224
218
|
const ctx = {
|
|
225
|
-
|
|
219
|
+
// base URI for resolving source description URLs honors Arazzo `$self` field
|
|
220
|
+
baseURI: resolveArazzo$selfField(retrievalURI, api),
|
|
226
221
|
options,
|
|
227
222
|
strategyName,
|
|
228
223
|
currentDepth,
|
|
229
|
-
|
|
224
|
+
ancestors,
|
|
225
|
+
dereferenced
|
|
230
226
|
};
|
|
231
227
|
|
|
232
228
|
// determine which source descriptions to dereference (array filters by name)
|
|
@@ -237,11 +233,12 @@ export async function dereferenceSourceDescriptions(parseResult, parseResultRetr
|
|
|
237
233
|
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
238
234
|
}) : api.sourceDescriptions;
|
|
239
235
|
|
|
240
|
-
// process sequentially to ensure proper cycle detection with shared
|
|
236
|
+
// process sequentially to ensure proper cycle detection with shared state
|
|
241
237
|
for (const sourceDescription of sourceDescriptions) {
|
|
242
238
|
const sourceDescriptionDereferenceResult = await dereferenceSourceDescription(sourceDescription, ctx);
|
|
243
|
-
// always attach result (even on failure - contains annotations)
|
|
244
|
-
|
|
239
|
+
// always attach result (even on failure - contains annotations);
|
|
240
|
+
// a shared document attaches the result where it was dereferenced
|
|
241
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult.meta.get('parseResult') ?? sourceDescriptionDereferenceResult);
|
|
245
242
|
results.push(sourceDescriptionDereferenceResult);
|
|
246
243
|
}
|
|
247
244
|
return results;
|
|
@@ -4,7 +4,7 @@ var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopReq
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.resolveArazzo$selfField = exports.refractToJSONSchemaElement = exports.maybeRefractToJSONSchemaElement = exports.identifiedBy$self = void 0;
|
|
7
|
+
exports.resolveArazzo$selfField = exports.refractToJSONSchemaElement = exports.maybeRefractToJSONSchemaElement = exports.identifiedBy$self = exports.arazzoDocumentURIs = void 0;
|
|
8
8
|
Object.defineProperty(exports, "resolveSchema$idField", {
|
|
9
9
|
enumerable: true,
|
|
10
10
|
get: function () {
|
|
@@ -17,9 +17,13 @@ Object.defineProperty(exports, "resolveSchema$refField", {
|
|
|
17
17
|
return _util.resolveSchema$refField;
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
|
+
exports.validateSourceDescriptionAPI = void 0;
|
|
20
21
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
21
22
|
var _apidomCore = require("@speclynx/apidom-core");
|
|
22
23
|
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
24
|
+
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
25
|
+
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
26
|
+
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
23
27
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
24
28
|
var _util = require("../openapi-3-1/util.cjs");
|
|
25
29
|
/**
|
|
@@ -44,6 +48,47 @@ const resolveArazzo$selfField = (retrievalURI, element) => {
|
|
|
44
48
|
return url.resolve(retrievalURI, url.sanitize(url.stripHash($self)));
|
|
45
49
|
};
|
|
46
50
|
|
|
51
|
+
/**
|
|
52
|
+
* URIs an Arazzo document is known by: its retrieval URI and, when the
|
|
53
|
+
* document carries a `$self` field resolving elsewhere, its identity.
|
|
54
|
+
*/
|
|
55
|
+
exports.resolveArazzo$selfField = resolveArazzo$selfField;
|
|
56
|
+
const arazzoDocumentURIs = (retrievalURI, element) => {
|
|
57
|
+
const identity = resolveArazzo$selfField(retrievalURI, element);
|
|
58
|
+
return identity === retrievalURI ? [retrievalURI] : [retrievalURI, identity];
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Annotates a source description result when the document is not an OpenAPI
|
|
63
|
+
* or Arazzo document, or does not match the type the source description declares.
|
|
64
|
+
*/
|
|
65
|
+
exports.arazzoDocumentURIs = arazzoDocumentURIs;
|
|
66
|
+
const validateSourceDescriptionAPI = (parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI) => {
|
|
67
|
+
// only allow OpenAPI and Arazzo as source descriptions
|
|
68
|
+
const isOpenApi = (0, _apidomNsOpenapi.isSwaggerElement)(sourceDescriptionAPI) || (0, _apidomNsOpenapi2.isOpenApi3_0Element)(sourceDescriptionAPI) || (0, _apidomNsOpenapi3.isOpenApi3_1Element)(sourceDescriptionAPI);
|
|
69
|
+
const isArazzo = (0, _apidomNsArazzo.isArazzoSpecification1Element)(sourceDescriptionAPI);
|
|
70
|
+
if (!isOpenApi && !isArazzo) {
|
|
71
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
72
|
+
annotation.classes.push('warning');
|
|
73
|
+
parseResult.push(annotation);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// validate declared type matches actual type
|
|
78
|
+
const declaredType = (0, _apidomCore.toValue)(sourceDescription.type);
|
|
79
|
+
if (typeof declaredType === 'string') {
|
|
80
|
+
if (declaredType === 'openapi' && !isOpenApi) {
|
|
81
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but is an Arazzo document`);
|
|
82
|
+
annotation.classes.push('warning');
|
|
83
|
+
parseResult.push(annotation);
|
|
84
|
+
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
85
|
+
const annotation = new _apidomDatamodel.AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but is an OpenAPI document`);
|
|
86
|
+
annotation.classes.push('warning');
|
|
87
|
+
parseResult.push(annotation);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
47
92
|
/**
|
|
48
93
|
* Predicate matching a Reference by the identity (`$self`) of the Arazzo
|
|
49
94
|
* document it holds, rather than by its retrieval URI. Implements identity-based
|
|
@@ -52,7 +97,7 @@ const resolveArazzo$selfField = (retrievalURI, element) => {
|
|
|
52
97
|
*
|
|
53
98
|
* @public
|
|
54
99
|
*/
|
|
55
|
-
exports.
|
|
100
|
+
exports.validateSourceDescriptionAPI = validateSourceDescriptionAPI;
|
|
56
101
|
const identifiedBy$self = uri => reference => {
|
|
57
102
|
const result = reference.value?.result;
|
|
58
103
|
return (0, _apidomNsArazzo.isArazzoSpecification1Element)(result) && resolveArazzo$selfField(reference.uri, result) === uri;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { isPrimitiveElement } from '@speclynx/apidom-datamodel';
|
|
1
|
+
import { AnnotationElement, isPrimitiveElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { toValue } from '@speclynx/apidom-core';
|
|
3
3
|
import { refractJSONSchema, isArazzoSpecification1Element } from '@speclynx/apidom-ns-arazzo-1';
|
|
4
|
+
import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
|
|
5
|
+
import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
|
|
6
|
+
import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
|
|
4
7
|
import * as url from "../../../util/url.mjs";
|
|
5
8
|
export { resolveSchema$refField, resolveSchema$idField } from "../openapi-3-1/util.mjs";
|
|
6
9
|
/**
|
|
@@ -25,6 +28,45 @@ export const resolveArazzo$selfField = (retrievalURI, element) => {
|
|
|
25
28
|
return url.resolve(retrievalURI, url.sanitize(url.stripHash($self)));
|
|
26
29
|
};
|
|
27
30
|
|
|
31
|
+
/**
|
|
32
|
+
* URIs an Arazzo document is known by: its retrieval URI and, when the
|
|
33
|
+
* document carries a `$self` field resolving elsewhere, its identity.
|
|
34
|
+
*/
|
|
35
|
+
export const arazzoDocumentURIs = (retrievalURI, element) => {
|
|
36
|
+
const identity = resolveArazzo$selfField(retrievalURI, element);
|
|
37
|
+
return identity === retrievalURI ? [retrievalURI] : [retrievalURI, identity];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Annotates a source description result when the document is not an OpenAPI
|
|
42
|
+
* or Arazzo document, or does not match the type the source description declares.
|
|
43
|
+
*/
|
|
44
|
+
export const validateSourceDescriptionAPI = (parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI) => {
|
|
45
|
+
// only allow OpenAPI and Arazzo as source descriptions
|
|
46
|
+
const isOpenApi = isSwaggerElement(sourceDescriptionAPI) || isOpenApi3_0Element(sourceDescriptionAPI) || isOpenApi3_1Element(sourceDescriptionAPI);
|
|
47
|
+
const isArazzo = isArazzoSpecification1Element(sourceDescriptionAPI);
|
|
48
|
+
if (!isOpenApi && !isArazzo) {
|
|
49
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
50
|
+
annotation.classes.push('warning');
|
|
51
|
+
parseResult.push(annotation);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// validate declared type matches actual type
|
|
56
|
+
const declaredType = toValue(sourceDescription.type);
|
|
57
|
+
if (typeof declaredType === 'string') {
|
|
58
|
+
if (declaredType === 'openapi' && !isOpenApi) {
|
|
59
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but is an Arazzo document`);
|
|
60
|
+
annotation.classes.push('warning');
|
|
61
|
+
parseResult.push(annotation);
|
|
62
|
+
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
63
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but is an OpenAPI document`);
|
|
64
|
+
annotation.classes.push('warning');
|
|
65
|
+
parseResult.push(annotation);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
28
70
|
/**
|
|
29
71
|
* Predicate matching a Reference by the identity (`$self`) of the Arazzo
|
|
30
72
|
* document it holds, rather than by its retrieval URI. Implements identity-based
|