@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speclynx/apidom-reference",
3
- "version": "5.2.1",
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.1",
303
- "@speclynx/apidom-datamodel": "5.2.1",
304
- "@speclynx/apidom-error": "5.2.1",
305
- "@speclynx/apidom-json-pointer": "5.2.1",
306
- "@speclynx/apidom-ns-arazzo-1": "5.2.1",
307
- "@speclynx/apidom-ns-asyncapi-2": "5.2.1",
308
- "@speclynx/apidom-ns-json-schema-2020-12": "5.2.1",
309
- "@speclynx/apidom-ns-openapi-2": "5.2.1",
310
- "@speclynx/apidom-ns-openapi-3-0": "5.2.1",
311
- "@speclynx/apidom-ns-openapi-3-1": "5.2.1",
312
- "@speclynx/apidom-ns-overlay-1": "5.2.1",
313
- "@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.1",
314
- "@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.1",
315
- "@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.1",
316
- "@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.1",
317
- "@speclynx/apidom-parser-adapter-json": "5.2.1",
318
- "@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.1",
319
- "@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.1",
320
- "@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.1",
321
- "@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.1",
322
- "@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.1",
323
- "@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.1",
324
- "@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.1",
325
- "@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.1",
326
- "@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.1",
327
- "@speclynx/apidom-traverse": "5.2.1",
328
- "@swaggerexpert/arazzo-runtime-expression": "^3.1.0",
329
- "axios": "^1.19.0",
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": "814e67e36b1e27546288c74a25eab8f9463943eb"
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 already visited (cycle detection)
48
- if (ctx.visitedUrls.has(retrievalURI)) {
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
- ctx.visitedUrls.add(retrievalURI);
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
- sourceDescriptionsVisitedUrls: ctx.visitedUrls
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
- sourceDescriptionsVisitedUrls: ctx.visitedUrls
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
- // only allow OpenAPI and Arazzo as source descriptions
126
+ // register dereferenced document for later references to it
117
127
  const {
118
128
  api: sourceDescriptionAPI
119
129
  } = parseResult;
120
- const isOpenApi = (0, _apidomNsOpenapi.isSwaggerElement)(sourceDescriptionAPI) || (0, _apidomNsOpenapi2.isOpenApi3_0Element)(sourceDescriptionAPI) || (0, _apidomNsOpenapi3.isOpenApi3_1Element)(sourceDescriptionAPI);
121
- const isArazzo = (0, _apidomNsArazzo.isArazzoSpecification1Element)(sourceDescriptionAPI);
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 visitedUrls = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
217
-
218
- // base URI for resolving source description URLs honors Arazzo `$self` field
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
- baseURI,
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
- visitedUrls
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 visitedUrls
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
- sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult);
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 already visited (cycle detection)
41
- if (ctx.visitedUrls.has(retrievalURI)) {
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
- ctx.visitedUrls.add(retrievalURI);
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
- sourceDescriptionsVisitedUrls: ctx.visitedUrls
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
- sourceDescriptionsVisitedUrls: ctx.visitedUrls
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
- // only allow OpenAPI and Arazzo as source descriptions
119
+ // register dereferenced document for later references to it
110
120
  const {
111
121
  api: sourceDescriptionAPI
112
122
  } = parseResult;
113
- const isOpenApi = isSwaggerElement(sourceDescriptionAPI) || isOpenApi3_0Element(sourceDescriptionAPI) || isOpenApi3_1Element(sourceDescriptionAPI);
114
- const isArazzo = isArazzoSpecification1Element(sourceDescriptionAPI);
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 visitedUrls = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
210
-
211
- // base URI for resolving source description URLs honors Arazzo `$self` field
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
- baseURI,
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
- visitedUrls
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 visitedUrls
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
- sourceDescription.meta.set('parseResult', sourceDescriptionDereferenceResult);
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.resolveArazzo$selfField = resolveArazzo$selfField;
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