swagger-client 3.26.8 → 3.27.1

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.
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable camelcase */
2
- import { isObjectElement, isPrimitiveElement, isStringElement, isMemberElement, IdentityManager, visit, includesClasses, toValue, cloneShallow, cloneDeep } from '@swagger-api/apidom-core';
2
+ import { RefElement, isObjectElement, isPrimitiveElement, isStringElement, isMemberElement, IdentityManager, visit, includesClasses, toValue, cloneShallow, cloneDeep } from '@swagger-api/apidom-core';
3
3
  import { ApiDOMError } from '@swagger-api/apidom-error';
4
- import { isReferenceLikeElement, isBooleanJsonSchemaElement, ReferenceElement, PathItemElement, SchemaElement, getNodeType, keyMap } from '@swagger-api/apidom-ns-openapi-3-1';
4
+ import { isReferenceLikeElement, isReferenceElement, isBooleanJsonSchemaElement, isPathItemElement, isSchemaElement, ReferenceElement, PathItemElement, SchemaElement, getNodeType, keyMap } from '@swagger-api/apidom-ns-openapi-3-1';
5
5
  import { evaluate as jsonPointerEvaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
6
6
  import { url, MaximumDereferenceDepthError, File } from '@swagger-api/apidom-reference/configuration/empty';
7
7
  import { OpenApi3_1DereferenceVisitor, resolveSchema$refField, maybeRefractToSchemaElement } from '@swagger-api/apidom-reference/dereference/strategies/openapi-3-1';
@@ -18,12 +18,6 @@ const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
18
18
 
19
19
  // initialize element identity manager
20
20
  const identityManager = IdentityManager();
21
-
22
- /**
23
- * Predicate for detecting if element was created by merging referencing
24
- * element with particular element identity with a referenced element.
25
- */
26
- const wasReferencedBy = referencingElement => element => element.meta.hasKey('ref-referencing-element-id') && element.meta.get('ref-referencing-element-id').equals(toValue(identityManager.identify(referencingElement)));
27
21
  const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.compose({
28
22
  props: {
29
23
  useCircularStructures: true,
@@ -42,18 +36,11 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
42
36
  methods: {
43
37
  async ReferenceElement(referencingElement, key, parent, path, ancestors) {
44
38
  try {
45
- var _this$basePath;
46
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
47
-
48
- // skip already identified cycled Path Item Objects
49
- if (includesClasses(['cycle'], referencingElement.$ref)) {
50
- return false;
51
- }
52
-
53
- // detect possible cycle in traversal and avoid it
54
- if (ancestorsLineage.includesCycle(referencingElement)) {
39
+ // skip current referencing element as it's already been access
40
+ if (this.indirections.includes(referencingElement)) {
55
41
  return false;
56
42
  }
43
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
57
44
  const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
58
45
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
59
46
  const isExternalReference = !isInternalReference;
@@ -73,6 +60,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
73
60
 
74
61
  // possibly non-semantic fragment
75
62
  let referencedElement = jsonPointerEvaluate(jsonPointer, reference.value.result);
63
+ referencedElement.id = identityManager.identify(referencedElement);
76
64
 
77
65
  // applying semantics to a fragment
78
66
  if (isPrimitiveElement(referencedElement)) {
@@ -94,105 +82,124 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
94
82
  }
95
83
 
96
84
  // detect direct or indirect reference
97
- if (this.indirections.includes(referencedElement)) {
98
- throw new ApiDOMError('Recursive JSON Pointer detected');
85
+ if (referencingElement === referencedElement) {
86
+ throw new ApiDOMError('Recursive Reference Object detected');
99
87
  }
100
88
 
101
89
  // detect maximum depth of dereferencing
102
90
  if (this.indirections.length > this.options.dereference.maxDepth) {
103
91
  throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
104
92
  }
105
- if (!this.useCircularStructures) {
106
- const hasCycles = ancestorsLineage.includes(referencedElement);
107
- if (hasCycles) {
108
- if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) {
109
- // make the referencing URL or file system path absolute
110
- const cycledReferenceElement = new ReferenceElement({
111
- $ref: $refBaseURI
112
- }, cloneDeep(referencingElement.meta), cloneDeep(referencingElement.attributes));
113
- cycledReferenceElement.get('$ref').classes.push('cycle');
114
- return cycledReferenceElement;
93
+
94
+ // detect second deep dive into the same fragment and avoid it
95
+ if (ancestorsLineage.includes(referencedElement)) {
96
+ reference.refSet.circular = true;
97
+ if (this.options.dereference.circular === 'error') {
98
+ throw new ApiDOMError('Circular reference detected');
99
+ } else if (this.options.dereference.circular === 'replace') {
100
+ var _this$options$derefer, _this$options$derefer2;
101
+ const refElement = new RefElement(referencedElement.id, {
102
+ type: 'reference',
103
+ uri: reference.uri,
104
+ $ref: toValue(referencingElement.$ref),
105
+ baseURI: $refBaseURI,
106
+ referencingElement
107
+ });
108
+ const replacer = (_this$options$derefer = (_this$options$derefer2 = this.options.dereference.strategyOpts['openapi-3-1']) === null || _this$options$derefer2 === void 0 ? void 0 : _this$options$derefer2.circularReplacer) !== null && _this$options$derefer !== void 0 ? _this$options$derefer : this.options.dereference.circularReplacer;
109
+ const replacement = replacer(refElement);
110
+ if (isMemberElement(parent)) {
111
+ parent.value = replacement; // eslint-disable-line no-param-reassign
112
+ } else if (Array.isArray(parent)) {
113
+ parent[key] = replacement; // eslint-disable-line no-param-reassign
115
114
  }
116
- // skip processing this reference
117
- return false;
115
+ return !parent ? replacement : false;
118
116
  }
119
117
  }
120
118
 
121
- // append referencing schema to ancestors lineage
122
- directAncestors.add(referencingElement);
123
-
124
- // dive deep into the fragment
125
- const visitor = OpenApi3_1SwaggerClientDereferenceVisitor({
126
- reference,
127
- namespace: this.namespace,
128
- indirections: [...this.indirections],
129
- options: this.options,
130
- ancestors: ancestorsLineage,
131
- allowMetaPatches: this.allowMetaPatches,
132
- useCircularStructures: this.useCircularStructures,
133
- basePath: (_this$basePath = this.basePath) !== null && _this$basePath !== void 0 ? _this$basePath : [...toPath([...ancestors, parent, referencingElement]), '$ref']
134
- });
135
- referencedElement = await visitAsync(referencedElement, visitor, {
136
- keyMap,
137
- nodeTypeGetter: getNodeType
138
- });
119
+ /**
120
+ * Dive deep into the fragment.
121
+ *
122
+ * Cases to consider:
123
+ * 1. We're crossing document boundary
124
+ * 2. Fragment is from non-root document
125
+ * 3. Fragment is a Reference Object. We need to follow it to get the eventual value
126
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
127
+ */
128
+ const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
129
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
130
+ if ((isExternalReference || isNonRootDocument || isReferenceElement(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
131
+ var _this$basePath;
132
+ // append referencing reference to ancestors lineage
133
+ directAncestors.add(referencingElement);
134
+ const visitor = OpenApi3_1SwaggerClientDereferenceVisitor({
135
+ reference,
136
+ namespace: this.namespace,
137
+ indirections: [...this.indirections],
138
+ options: this.options,
139
+ refractCache: this.refractCache,
140
+ ancestors: ancestorsLineage,
141
+ allowMetaPatches: this.allowMetaPatches,
142
+ useCircularStructures: this.useCircularStructures,
143
+ basePath: (_this$basePath = this.basePath) !== null && _this$basePath !== void 0 ? _this$basePath : [...toPath([...ancestors, parent, referencingElement]), '$ref']
144
+ });
145
+ referencedElement = await visitAsync(referencedElement, visitor, {
146
+ keyMap,
147
+ nodeTypeGetter: getNodeType
148
+ });
139
149
 
140
- // remove referencing schema from ancestors lineage
141
- directAncestors.delete(referencingElement);
150
+ // remove referencing reference from ancestors lineage
151
+ directAncestors.delete(referencingElement);
152
+ }
142
153
  this.indirections.pop();
143
- const mergeAndAnnotateReferencedElement = refedElement => {
144
- const copy = cloneShallow(refedElement);
145
-
146
- // annotate fragment with info about original Reference element
147
- copy.setMetaProperty('ref-fields', {
148
- $ref: toValue(referencingElement.$ref),
149
- description: toValue(referencingElement.description),
150
- summary: toValue(referencingElement.summary)
151
- });
152
- // annotate fragment with info about origin
153
- copy.setMetaProperty('ref-origin', reference.uri);
154
- // annotate fragment with info about referencing element
155
- copy.setMetaProperty('ref-referencing-element-id', cloneDeep(identityManager.identify(referencingElement)));
154
+ const mergedElement = cloneShallow(referencedElement);
156
155
 
157
- // override description and summary (outer has higher priority then inner)
158
- if (isObjectElement(refedElement)) {
159
- if (referencingElement.hasKey('description') && 'description' in refedElement) {
160
- copy.remove('description');
161
- copy.set('description', referencingElement.get('description'));
162
- }
163
- if (referencingElement.hasKey('summary') && 'summary' in refedElement) {
164
- copy.remove('summary');
165
- copy.set('summary', referencingElement.get('summary'));
166
- }
156
+ // annotate fragment with info about original Reference element
157
+ mergedElement.setMetaProperty('ref-fields', {
158
+ $ref: toValue(referencingElement.$ref),
159
+ description: toValue(referencingElement.description),
160
+ summary: toValue(referencingElement.summary)
161
+ });
162
+ // annotate fragment with info about origin
163
+ mergedElement.setMetaProperty('ref-origin', reference.uri);
164
+ // annotate fragment with info about referencing element
165
+ mergedElement.setMetaProperty('ref-referencing-element-id', cloneDeep(identityManager.identify(referencingElement)));
166
+
167
+ // override description and summary (outer has higher priority then inner)
168
+ if (isObjectElement(referencedElement)) {
169
+ if (referencingElement.hasKey('description') && 'description' in referencedElement) {
170
+ mergedElement.remove('description');
171
+ mergedElement.set('description', referencingElement.get('description'));
167
172
  }
168
-
169
- // apply meta patches
170
- if (this.allowMetaPatches && isObjectElement(copy)) {
171
- // apply meta patch only when not already applied
172
- if (!copy.hasKey('$$ref')) {
173
- const baseURI = url.resolve(retrievalURI, $refBaseURI);
174
- copy.set('$$ref', baseURI);
175
- }
173
+ if (referencingElement.hasKey('summary') && 'summary' in referencedElement) {
174
+ mergedElement.remove('summary');
175
+ mergedElement.set('summary', referencingElement.get('summary'));
176
176
  }
177
- return copy;
178
- };
177
+ }
179
178
 
180
- // attempting to create cycle
181
- if (ancestorsLineage.includes(referencingElement) || ancestorsLineage.includes(referencedElement)) {
182
- var _ancestorsLineage$fin;
183
- const replaceWith = (_ancestorsLineage$fin = ancestorsLineage.findItem(wasReferencedBy(referencingElement))) !== null && _ancestorsLineage$fin !== void 0 ? _ancestorsLineage$fin : mergeAndAnnotateReferencedElement(referencedElement);
184
- if (isMemberElement(parent)) {
185
- parent.value = replaceWith; // eslint-disable-line no-param-reassign
186
- } else if (Array.isArray(parent)) {
187
- parent[key] = replaceWith; // eslint-disable-line no-param-reassign
179
+ // apply meta patches
180
+ if (this.allowMetaPatches && isObjectElement(mergedElement)) {
181
+ // apply meta patch only when not already applied
182
+ if (!mergedElement.hasKey('$$ref')) {
183
+ const baseURI = url.resolve(retrievalURI, $refBaseURI);
184
+ mergedElement.set('$$ref', baseURI);
188
185
  }
189
- return false;
190
186
  }
191
187
 
192
- // transclude the element for a fragment
193
- return mergeAndAnnotateReferencedElement(referencedElement);
188
+ /**
189
+ * Transclude referencing element with merged referenced element.
190
+ */
191
+ if (isMemberElement(parent)) {
192
+ parent.value = mergedElement; // eslint-disable-line no-param-reassign
193
+ } else if (Array.isArray(parent)) {
194
+ parent[key] = mergedElement; // eslint-disable-line no-param-reassign
195
+ }
196
+
197
+ /**
198
+ * We're at the root of the tree, so we're just replacing the entire tree.
199
+ */
200
+ return !parent ? mergedElement : false;
194
201
  } catch (error) {
195
- var _this$basePath2, _this$options$derefer, _this$options$derefer2;
202
+ var _this$basePath2, _this$options$derefer3, _this$options$derefer4;
196
203
  const rootCause = getRootCause(error);
197
204
  const wrappedError = wrapError(rootCause, {
198
205
  baseDoc: this.reference.uri,
@@ -200,29 +207,27 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
200
207
  pointer: uriToPointer(toValue(referencingElement.$ref)),
201
208
  fullPath: (_this$basePath2 = this.basePath) !== null && _this$basePath2 !== void 0 ? _this$basePath2 : [...toPath([...ancestors, parent, referencingElement]), '$ref']
202
209
  });
203
- (_this$options$derefer = this.options.dereference.dereferenceOpts) === null || _this$options$derefer === void 0 || (_this$options$derefer = _this$options$derefer.errors) === null || _this$options$derefer === void 0 || (_this$options$derefer2 = _this$options$derefer.push) === null || _this$options$derefer2 === void 0 || _this$options$derefer2.call(_this$options$derefer, wrappedError);
210
+ (_this$options$derefer3 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer3 === void 0 || (_this$options$derefer3 = _this$options$derefer3.errors) === null || _this$options$derefer3 === void 0 || (_this$options$derefer4 = _this$options$derefer3.push) === null || _this$options$derefer4 === void 0 || _this$options$derefer4.call(_this$options$derefer3, wrappedError);
204
211
  return undefined;
205
212
  }
206
213
  },
207
214
  async PathItemElement(pathItemElement, key, parent, path, ancestors) {
208
215
  try {
209
- var _this$basePath3;
210
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
211
-
212
216
  // ignore PathItemElement without $ref field
213
217
  if (!isStringElement(pathItemElement.$ref)) {
214
218
  return undefined;
215
219
  }
216
220
 
217
- // skip already identified cycled Path Item Objects
218
- if (includesClasses(['cycle'], pathItemElement.$ref)) {
221
+ // skip current referencing element as it's already been access
222
+ if (this.indirections.includes(pathItemElement)) {
219
223
  return false;
220
224
  }
221
225
 
222
- // detect possible cycle in traversal and avoid it
223
- if (ancestorsLineage.includesCycle(pathItemElement)) {
226
+ // skip already identified cycled Path Item Objects
227
+ if (includesClasses(['cycle'], pathItemElement.$ref)) {
224
228
  return false;
225
229
  }
230
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
226
231
  const retrievalURI = this.toBaseURI(toValue(pathItemElement.$ref));
227
232
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
228
233
  const isExternalReference = !isInternalReference;
@@ -242,10 +247,11 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
242
247
 
243
248
  // possibly non-semantic referenced element
244
249
  let referencedElement = jsonPointerEvaluate(jsonPointer, reference.value.result);
250
+ referencedElement.id = identityManager.identify(referencedElement);
245
251
 
246
252
  // applying semantics to a referenced element
247
253
  if (isPrimitiveElement(referencedElement)) {
248
- const cacheKey = `pathItem-${toValue(identityManager.identify(referencedElement))}`;
254
+ const cacheKey = `path-item-${toValue(identityManager.identify(referencedElement))}`;
249
255
  if (this.refractCache.has(cacheKey)) {
250
256
  referencedElement = this.refractCache.get(cacheKey);
251
257
  } else {
@@ -255,55 +261,82 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
255
261
  }
256
262
 
257
263
  // detect direct or indirect reference
258
- if (this.indirections.includes(referencedElement)) {
259
- throw new ApiDOMError('Recursive JSON Pointer detected');
264
+ if (pathItemElement === referencedElement) {
265
+ throw new ApiDOMError('Recursive Path Item Object reference detected');
260
266
  }
261
267
 
262
268
  // detect maximum depth of dereferencing
263
269
  if (this.indirections.length > this.options.dereference.maxDepth) {
264
270
  throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
265
271
  }
266
- if (!this.useCircularStructures) {
267
- const hasCycles = ancestorsLineage.includes(referencedElement);
268
- if (hasCycles) {
269
- if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) {
270
- // make the referencing URL or file system path absolute
271
- const cycledPathItemElement = new PathItemElement({
272
- $ref: $refBaseURI
273
- }, cloneDeep(pathItemElement.meta), cloneDeep(pathItemElement.attributes));
274
- cycledPathItemElement.get('$ref').classes.push('cycle');
275
- return cycledPathItemElement;
272
+
273
+ // detect second deep dive into the same fragment and avoid it
274
+ if (ancestorsLineage.includes(referencedElement)) {
275
+ reference.refSet.circular = true;
276
+ if (this.options.dereference.circular === 'error') {
277
+ throw new ApiDOMError('Circular reference detected');
278
+ } else if (this.options.dereference.circular === 'replace') {
279
+ var _this$options$derefer5, _this$options$derefer6;
280
+ const refElement = new RefElement(referencedElement.id, {
281
+ type: 'path-item',
282
+ uri: reference.uri,
283
+ $ref: toValue(pathItemElement.$ref),
284
+ baseURI: $refBaseURI,
285
+ referencingElement: pathItemElement
286
+ });
287
+ const replacer = (_this$options$derefer5 = (_this$options$derefer6 = this.options.dereference.strategyOpts['openapi-3-1']) === null || _this$options$derefer6 === void 0 ? void 0 : _this$options$derefer6.circularReplacer) !== null && _this$options$derefer5 !== void 0 ? _this$options$derefer5 : this.options.dereference.circularReplacer;
288
+ const replacement = replacer(refElement);
289
+ if (isMemberElement(parent)) {
290
+ parent.value = replacement; // eslint-disable-line no-param-reassign
291
+ } else if (Array.isArray(parent)) {
292
+ parent[key] = replacement; // eslint-disable-line no-param-reassign
276
293
  }
277
- // skip processing this path item and all it's child elements
278
- return false;
294
+ return !parent ? replacement : false;
279
295
  }
280
296
  }
281
297
 
282
- // append referencing schema to ancestors lineage
283
- directAncestors.add(pathItemElement);
284
-
285
- // dive deep into the referenced element
286
- const visitor = OpenApi3_1SwaggerClientDereferenceVisitor({
287
- reference,
288
- namespace: this.namespace,
289
- indirections: [...this.indirections],
290
- options: this.options,
291
- ancestors: ancestorsLineage,
292
- allowMetaPatches: this.allowMetaPatches,
293
- useCircularStructures: this.useCircularStructures,
294
- basePath: (_this$basePath3 = this.basePath) !== null && _this$basePath3 !== void 0 ? _this$basePath3 : [...toPath([...ancestors, parent, pathItemElement]), '$ref']
295
- });
296
- referencedElement = await visitAsync(referencedElement, visitor, {
297
- keyMap,
298
- nodeTypeGetter: getNodeType
299
- });
298
+ /**
299
+ * Dive deep into the fragment.
300
+ *
301
+ * Cases to consider:
302
+ * 1. We're crossing document boundary
303
+ * 2. Fragment is from non-root document
304
+ * 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
305
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
306
+ */
307
+ const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
308
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
309
+ if ((isExternalReference || isNonRootDocument || isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
310
+ var _this$basePath3;
311
+ // append referencing schema to ancestors lineage
312
+ directAncestors.add(pathItemElement);
313
+
314
+ // dive deep into the referenced element
315
+ const visitor = OpenApi3_1SwaggerClientDereferenceVisitor({
316
+ reference,
317
+ namespace: this.namespace,
318
+ indirections: [...this.indirections],
319
+ options: this.options,
320
+ ancestors: ancestorsLineage,
321
+ allowMetaPatches: this.allowMetaPatches,
322
+ useCircularStructures: this.useCircularStructures,
323
+ basePath: (_this$basePath3 = this.basePath) !== null && _this$basePath3 !== void 0 ? _this$basePath3 : [...toPath([...ancestors, parent, pathItemElement]), '$ref']
324
+ });
325
+ referencedElement = await visitAsync(referencedElement, visitor, {
326
+ keyMap,
327
+ nodeTypeGetter: getNodeType
328
+ });
300
329
 
301
- // remove referencing schema from ancestors lineage
302
- directAncestors.delete(pathItemElement);
330
+ // remove referencing schema from ancestors lineage
331
+ directAncestors.delete(pathItemElement);
332
+ }
303
333
  this.indirections.pop();
304
- const mergeAndAnnotateReferencedElement = refedElement => {
305
- // merge fields from referenced Path Item with referencing one
306
- const mergedElement = new PathItemElement([...refedElement.content], cloneDeep(refedElement.meta), cloneDeep(refedElement.attributes));
334
+
335
+ /**
336
+ * Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
337
+ */
338
+ if (isPathItemElement(referencedElement)) {
339
+ const mergedElement = new PathItemElement([...referencedElement.content], cloneDeep(referencedElement.meta), cloneDeep(referencedElement.attributes));
307
340
  // existing keywords from referencing PathItemElement overrides ones from referenced element
308
341
  pathItemElement.forEach((value, keyElement, item) => {
309
342
  mergedElement.remove(toValue(keyElement));
@@ -328,25 +361,24 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
328
361
  mergedElement.set('$$ref', baseURI);
329
362
  }
330
363
  }
331
- return mergedElement;
332
- };
364
+ referencedElement = mergedElement;
365
+ }
333
366
 
334
- // attempting to create cycle
335
- if (ancestorsLineage.includes(pathItemElement) || ancestorsLineage.includes(referencedElement)) {
336
- var _ancestorsLineage$fin2;
337
- const replaceWith = (_ancestorsLineage$fin2 = ancestorsLineage.findItem(wasReferencedBy(pathItemElement))) !== null && _ancestorsLineage$fin2 !== void 0 ? _ancestorsLineage$fin2 : mergeAndAnnotateReferencedElement(referencedElement);
338
- if (isMemberElement(parent)) {
339
- parent.value = replaceWith; // eslint-disable-line no-param-reassign
340
- } else if (Array.isArray(parent)) {
341
- parent[key] = replaceWith; // eslint-disable-line no-param-reassign
342
- }
343
- return false;
367
+ /**
368
+ * Transclude referencing element with merged referenced element.
369
+ */
370
+ if (isMemberElement(parent)) {
371
+ parent.value = referencedElement; // eslint-disable-line no-param-reassign
372
+ } else if (Array.isArray(parent)) {
373
+ parent[key] = referencedElement; // eslint-disable-line no-param-reassign
344
374
  }
345
375
 
346
- // transclude referencing element with merged referenced element
347
- return mergeAndAnnotateReferencedElement(referencedElement);
376
+ /**
377
+ * We're at the root of the tree, so we're just replacing the entire tree.
378
+ */
379
+ return !parent ? referencedElement : undefined;
348
380
  } catch (error) {
349
- var _this$basePath4, _this$options$derefer3, _this$options$derefer4;
381
+ var _this$basePath4, _this$options$derefer7, _this$options$derefer8;
350
382
  const rootCause = getRootCause(error);
351
383
  const wrappedError = wrapError(rootCause, {
352
384
  baseDoc: this.reference.uri,
@@ -354,30 +386,23 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
354
386
  pointer: uriToPointer(toValue(pathItemElement.$ref)),
355
387
  fullPath: (_this$basePath4 = this.basePath) !== null && _this$basePath4 !== void 0 ? _this$basePath4 : [...toPath([...ancestors, parent, pathItemElement]), '$ref']
356
388
  });
357
- (_this$options$derefer3 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer3 === void 0 || (_this$options$derefer3 = _this$options$derefer3.errors) === null || _this$options$derefer3 === void 0 || (_this$options$derefer4 = _this$options$derefer3.push) === null || _this$options$derefer4 === void 0 || _this$options$derefer4.call(_this$options$derefer3, wrappedError);
389
+ (_this$options$derefer7 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer7 === void 0 || (_this$options$derefer7 = _this$options$derefer7.errors) === null || _this$options$derefer7 === void 0 || (_this$options$derefer8 = _this$options$derefer7.push) === null || _this$options$derefer8 === void 0 || _this$options$derefer8.call(_this$options$derefer7, wrappedError);
358
390
  return undefined;
359
391
  }
360
392
  },
361
393
  async SchemaElement(referencingElement, key, parent, path, ancestors) {
362
394
  try {
363
- var _this$basePath5;
364
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
365
-
366
395
  // skip current referencing schema as $ref keyword was not defined
367
396
  if (!isStringElement(referencingElement.$ref)) {
368
397
  // skip traversing this schema but traverse all it's child schemas
369
398
  return undefined;
370
399
  }
371
400
 
372
- // skip already identified cycled Path Item Objects
373
- if (includesClasses(['cycle'], referencingElement.$ref)) {
374
- return false;
375
- }
376
-
377
- // detect possible cycle in traversal and avoid it
378
- if (ancestorsLineage.includesCycle(referencingElement)) {
401
+ // skip current referencing element as it's already been access
402
+ if (this.indirections.includes(referencingElement)) {
379
403
  return false;
380
404
  }
405
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
381
406
 
382
407
  // compute baseURI using rules around $id and $ref keywords
383
408
  let reference = await this.toReference(url.unsanitize(this.reference.uri));
@@ -391,8 +416,8 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
391
416
  });
392
417
  const isUnknownURI = !this.options.resolve.resolvers.some(r => r.canRead(file));
393
418
  const isURL = !isUnknownURI;
394
- const isInternalReference = uri => url.stripHash(this.reference.uri) === uri;
395
- const isExternalReference = uri => !isInternalReference(uri);
419
+ let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI;
420
+ let isExternalReference = !isInternalReference;
396
421
  this.indirections.push(referencingElement);
397
422
 
398
423
  // determining reference, proper evaluation and selection mechanism
@@ -400,25 +425,45 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
400
425
  try {
401
426
  if (isUnknownURI || isURL) {
402
427
  // we're dealing with canonical URI or URL with possible fragment
428
+ retrievalURI = this.toBaseURI($refBaseURI);
403
429
  const selector = $refBaseURI;
404
- referencedElement = uriEvaluate(selector, maybeRefractToSchemaElement(reference.value.result));
430
+ const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
431
+ referencedElement = uriEvaluate(selector, referenceAsSchema);
432
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
433
+ referencedElement.id = identityManager.identify(referencedElement);
434
+
435
+ // ignore resolving internal Schema Objects
436
+ if (!this.options.resolve.internal && isInternalReference) {
437
+ // skip traversing this schema element but traverse all it's child elements
438
+ return undefined;
439
+ }
440
+ // ignore resolving external Schema Objects
441
+ if (!this.options.resolve.external && isExternalReference) {
442
+ // skip traversing this schema element but traverse all it's child elements
443
+ return undefined;
444
+ }
405
445
  } else {
406
446
  // we're assuming here that we're dealing with JSON Pointer here
407
- retrievalURI = this.toBaseURI(toValue($refBaseURI));
447
+ retrievalURI = this.toBaseURI($refBaseURI);
448
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
449
+ isExternalReference = !isInternalReference;
408
450
 
409
451
  // ignore resolving internal Schema Objects
410
- if (!this.options.resolve.internal && isInternalReference(retrievalURI)) {
452
+ if (!this.options.resolve.internal && isInternalReference) {
411
453
  // skip traversing this schema element but traverse all it's child elements
412
454
  return undefined;
413
455
  }
414
456
  // ignore resolving external Schema Objects
415
- if (!this.options.resolve.external && isExternalReference(retrievalURI)) {
457
+ if (!this.options.resolve.external && isExternalReference) {
416
458
  // skip traversing this schema element but traverse all it's child elements
417
459
  return undefined;
418
460
  }
419
461
  reference = await this.toReference(url.unsanitize($refBaseURI));
420
462
  const selector = uriToPointer($refBaseURI);
421
- referencedElement = maybeRefractToSchemaElement(jsonPointerEvaluate(selector, reference.value.result));
463
+ const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
464
+ referencedElement = jsonPointerEvaluate(selector, referenceAsSchema);
465
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
466
+ referencedElement.id = identityManager.identify(referencedElement);
422
467
  }
423
468
  } catch (error) {
424
469
  /**
@@ -428,38 +473,47 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
428
473
  if (isURL && error instanceof EvaluationJsonSchemaUriError) {
429
474
  if (isAnchor(uriToAnchor($refBaseURI))) {
430
475
  // we're dealing with JSON Schema $anchor here
431
- retrievalURI = this.toBaseURI(toValue($refBaseURI));
476
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
477
+ isExternalReference = !isInternalReference;
432
478
 
433
479
  // ignore resolving internal Schema Objects
434
- if (!this.options.resolve.internal && isInternalReference(retrievalURI)) {
480
+ if (!this.options.resolve.internal && isInternalReference) {
435
481
  // skip traversing this schema element but traverse all it's child elements
436
482
  return undefined;
437
483
  }
438
484
  // ignore resolving external Schema Objects
439
- if (!this.options.resolve.external && isExternalReference(retrievalURI)) {
485
+ if (!this.options.resolve.external && isExternalReference) {
440
486
  // skip traversing this schema element but traverse all it's child elements
441
487
  return undefined;
442
488
  }
443
489
  reference = await this.toReference(url.unsanitize($refBaseURI));
444
490
  const selector = uriToAnchor($refBaseURI);
445
- referencedElement = $anchorEvaluate(selector, maybeRefractToSchemaElement(reference.value.result));
491
+ const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
492
+ referencedElement = $anchorEvaluate(selector, referenceAsSchema);
493
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
494
+ referencedElement.id = identityManager.identify(referencedElement);
446
495
  } else {
447
496
  // we're assuming here that we're dealing with JSON Pointer here
448
497
  retrievalURI = this.toBaseURI(toValue($refBaseURI));
498
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
499
+ isExternalReference = !isInternalReference;
449
500
 
450
501
  // ignore resolving internal Schema Objects
451
- if (!this.options.resolve.internal && isInternalReference(retrievalURI)) {
502
+ if (!this.options.resolve.internal && isInternalReference) {
452
503
  // skip traversing this schema element but traverse all it's child elements
453
504
  return undefined;
454
505
  }
455
506
  // ignore resolving external Schema Objects
456
- if (!this.options.resolve.external && isExternalReference(retrievalURI)) {
507
+ if (!this.options.resolve.external && isExternalReference) {
457
508
  // skip traversing this schema element but traverse all it's child elements
458
509
  return undefined;
459
510
  }
460
511
  reference = await this.toReference(url.unsanitize($refBaseURI));
461
512
  const selector = uriToPointer($refBaseURI);
462
- referencedElement = maybeRefractToSchemaElement(jsonPointerEvaluate(selector, reference.value.result));
513
+ const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
514
+ referencedElement = jsonPointerEvaluate(selector, referenceAsSchema);
515
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
516
+ referencedElement.id = identityManager.identify(referencedElement);
463
517
  }
464
518
  } else {
465
519
  throw error;
@@ -467,7 +521,7 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
467
521
  }
468
522
 
469
523
  // detect direct or indirect reference
470
- if (this.indirections.includes(referencedElement)) {
524
+ if (referencingElement === referencedElement) {
471
525
  throw new ApiDOMError('Recursive Schema Object reference detected');
472
526
  }
473
527
 
@@ -476,45 +530,66 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
476
530
  throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
477
531
  }
478
532
 
479
- // useCircularStructures option processing
480
- if (!this.useCircularStructures) {
481
- const hasCycles = ancestorsLineage.includes(referencedElement);
482
- if (hasCycles) {
483
- if (url.isHttpUrl(retrievalURI) || url.isFileSystemPath(retrievalURI)) {
484
- // make the referencing URL or file system path absolute
485
- const baseURI = url.resolve(retrievalURI, $refBaseURI);
486
- const cycledSchemaElement = new SchemaElement({
487
- $ref: baseURI
488
- }, cloneDeep(referencingElement.meta), cloneDeep(referencingElement.attributes));
489
- cycledSchemaElement.get('$ref').classes.push('cycle');
490
- return cycledSchemaElement;
533
+ // detect second deep dive into the same fragment and avoid it
534
+ if (ancestorsLineage.includes(referencedElement)) {
535
+ reference.refSet.circular = true;
536
+ if (this.options.dereference.circular === 'error') {
537
+ throw new ApiDOMError('Circular reference detected');
538
+ } else if (this.options.dereference.circular === 'replace') {
539
+ var _this$options$derefer9, _this$options$derefer10;
540
+ const refElement = new RefElement(referencedElement.id, {
541
+ type: 'json-schema',
542
+ uri: reference.uri,
543
+ $ref: toValue(referencingElement.$ref),
544
+ baseURI: url.resolve(retrievalURI, $refBaseURI),
545
+ referencingElement
546
+ });
547
+ const replacer = (_this$options$derefer9 = (_this$options$derefer10 = this.options.dereference.strategyOpts['openapi-3-1']) === null || _this$options$derefer10 === void 0 ? void 0 : _this$options$derefer10.circularReplacer) !== null && _this$options$derefer9 !== void 0 ? _this$options$derefer9 : this.options.dereference.circularReplacer;
548
+ const replacement = replacer(refElement);
549
+ if (isMemberElement(parent)) {
550
+ parent.value = replacement; // eslint-disable-line no-param-reassign
551
+ } else if (Array.isArray(parent)) {
552
+ parent[key] = replacement; // eslint-disable-line no-param-reassign
491
553
  }
492
- // skip processing this schema and all it's child schemas
493
- return false;
554
+ return !parent ? replacement : false;
494
555
  }
495
556
  }
496
557
 
497
- // append referencing schema to ancestors lineage
498
- directAncestors.add(referencingElement);
499
-
500
- // dive deep into the fragment
501
- const mergeVisitor = OpenApi3_1SwaggerClientDereferenceVisitor({
502
- reference,
503
- namespace: this.namespace,
504
- indirections: [...this.indirections],
505
- options: this.options,
506
- useCircularStructures: this.useCircularStructures,
507
- allowMetaPatches: this.allowMetaPatches,
508
- ancestors: ancestorsLineage,
509
- basePath: (_this$basePath5 = this.basePath) !== null && _this$basePath5 !== void 0 ? _this$basePath5 : [...toPath([...ancestors, parent, referencingElement]), '$ref']
510
- });
511
- referencedElement = await visitAsync(referencedElement, mergeVisitor, {
512
- keyMap,
513
- nodeTypeGetter: getNodeType
514
- });
558
+ /**
559
+ * Dive deep into the fragment.
560
+ *
561
+ * Cases to consider:
562
+ * 1. We're crossing document boundary
563
+ * 2. Fragment is from non-root document
564
+ * 3. Fragment is a Schema Object with $ref field. We need to follow it to get the eventual value
565
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
566
+ */
567
+ const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
568
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
569
+ if ((isExternalReference || isNonRootDocument || isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
570
+ var _this$basePath5;
571
+ // append referencing schema to ancestors lineage
572
+ directAncestors.add(referencingElement);
573
+
574
+ // dive deep into the fragment
575
+ const mergeVisitor = OpenApi3_1SwaggerClientDereferenceVisitor({
576
+ reference,
577
+ namespace: this.namespace,
578
+ indirections: [...this.indirections],
579
+ options: this.options,
580
+ useCircularStructures: this.useCircularStructures,
581
+ allowMetaPatches: this.allowMetaPatches,
582
+ ancestors: ancestorsLineage,
583
+ basePath: (_this$basePath5 = this.basePath) !== null && _this$basePath5 !== void 0 ? _this$basePath5 : [...toPath([...ancestors, parent, referencingElement]), '$ref']
584
+ });
585
+ referencedElement = await visitAsync(referencedElement, mergeVisitor, {
586
+ keyMap,
587
+ nodeTypeGetter: getNodeType
588
+ });
515
589
 
516
- // remove referencing schema from ancestors lineage
517
- directAncestors.delete(referencingElement);
590
+ // remove referencing schema from ancestors lineage
591
+ directAncestors.delete(referencingElement);
592
+ }
518
593
  this.indirections.pop();
519
594
  if (isBooleanJsonSchemaElement(referencedElement)) {
520
595
  const booleanJsonSchemaElement = cloneDeep(referencedElement);
@@ -526,11 +601,20 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
526
601
  booleanJsonSchemaElement.setMetaProperty('ref-origin', reference.uri);
527
602
  // annotate fragment with info about referencing element
528
603
  booleanJsonSchemaElement.setMetaProperty('ref-referencing-element-id', cloneDeep(identityManager.identify(referencingElement)));
529
- return booleanJsonSchemaElement;
604
+ if (isMemberElement(parent)) {
605
+ parent.value = booleanJsonSchemaElement; // eslint-disable-line no-param-reassign
606
+ } else if (Array.isArray(parent)) {
607
+ parent[key] = booleanJsonSchemaElement; // eslint-disable-line no-param-reassign
608
+ }
609
+ return !parent ? booleanJsonSchemaElement : false;
530
610
  }
531
- const mergeAndAnnotateReferencedElement = refedElement => {
611
+
612
+ /**
613
+ * Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
614
+ */
615
+ if (isSchemaElement(referencedElement)) {
532
616
  // Schema Object - merge keywords from referenced schema with referencing schema
533
- const mergedElement = new SchemaElement([...refedElement.content], cloneDeep(refedElement.meta), cloneDeep(refedElement.attributes));
617
+ const mergedElement = new SchemaElement([...referencedElement.content], cloneDeep(referencedElement.meta), cloneDeep(referencedElement.attributes));
534
618
  // existing keywords from referencing schema overrides ones from referenced schema
535
619
  referencingElement.forEach((value, keyElement, item) => {
536
620
  mergedElement.remove(toValue(keyElement));
@@ -554,32 +638,31 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
554
638
  mergedElement.set('$$ref', baseURI);
555
639
  }
556
640
  }
557
- return mergedElement;
558
- };
641
+ referencedElement = mergedElement;
642
+ }
559
643
 
560
- // attempting to create cycle
561
- if (ancestorsLineage.includes(referencingElement) || ancestorsLineage.includes(referencedElement)) {
562
- var _ancestorsLineage$fin3;
563
- const replaceWith = (_ancestorsLineage$fin3 = ancestorsLineage.findItem(wasReferencedBy(referencingElement))) !== null && _ancestorsLineage$fin3 !== void 0 ? _ancestorsLineage$fin3 : mergeAndAnnotateReferencedElement(referencedElement);
564
- if (isMemberElement(parent)) {
565
- parent.value = replaceWith; // eslint-disable-line no-param-reassign
566
- } else if (Array.isArray(parent)) {
567
- parent[key] = replaceWith; // eslint-disable-line no-param-reassign
568
- }
569
- return false;
644
+ /**
645
+ * Transclude referencing element with merged referenced element.
646
+ */
647
+ if (isMemberElement(parent)) {
648
+ parent.value = referencedElement; // eslint-disable-line no-param-reassign
649
+ } else if (Array.isArray(parent)) {
650
+ parent[key] = referencedElement; // eslint-disable-line no-param-reassign
570
651
  }
571
652
 
572
- // transclude referencing element with merged referenced element
573
- return mergeAndAnnotateReferencedElement(referencedElement);
653
+ /**
654
+ * We're at the root of the tree, so we're just replacing the entire tree.
655
+ */
656
+ return !parent ? referencedElement : undefined;
574
657
  } catch (error) {
575
- var _this$basePath6, _this$options$derefer5, _this$options$derefer6;
658
+ var _this$basePath6, _this$options$derefer11, _this$options$derefer12;
576
659
  const rootCause = getRootCause(error);
577
660
  const wrappedError = new SchemaRefError(`Could not resolve reference: ${rootCause.message}`, {
578
661
  baseDoc: this.reference.uri,
579
662
  $ref: toValue(referencingElement.$ref),
580
663
  fullPath: (_this$basePath6 = this.basePath) !== null && _this$basePath6 !== void 0 ? _this$basePath6 : [...toPath([...ancestors, parent, referencingElement]), '$ref']
581
664
  }, rootCause);
582
- (_this$options$derefer5 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer5 === void 0 || (_this$options$derefer5 = _this$options$derefer5.errors) === null || _this$options$derefer5 === void 0 || (_this$options$derefer6 = _this$options$derefer5.push) === null || _this$options$derefer6 === void 0 || _this$options$derefer6.call(_this$options$derefer5, wrappedError);
665
+ (_this$options$derefer11 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer11 === void 0 || (_this$options$derefer11 = _this$options$derefer11.errors) === null || _this$options$derefer11 === void 0 || (_this$options$derefer12 = _this$options$derefer11.push) === null || _this$options$derefer12 === void 0 || _this$options$derefer12.call(_this$options$derefer11, wrappedError);
583
666
  return undefined;
584
667
  }
585
668
  },
@@ -595,14 +678,14 @@ const OpenApi3_1SwaggerClientDereferenceVisitor = OpenApi3_1DereferenceVisitor.c
595
678
  try {
596
679
  return await OpenApi3_1DereferenceVisitor.compose.methods.ExampleElement.call(this, exampleElement, key, parent, path, ancestors);
597
680
  } catch (error) {
598
- var _this$basePath7, _this$options$derefer7, _this$options$derefer8;
681
+ var _this$basePath7, _this$options$derefer13, _this$options$derefer14;
599
682
  const rootCause = getRootCause(error);
600
683
  const wrappedError = wrapError(rootCause, {
601
684
  baseDoc: this.reference.uri,
602
685
  externalValue: toValue(exampleElement.externalValue),
603
686
  fullPath: (_this$basePath7 = this.basePath) !== null && _this$basePath7 !== void 0 ? _this$basePath7 : [...toPath([...ancestors, parent, exampleElement]), 'externalValue']
604
687
  });
605
- (_this$options$derefer7 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer7 === void 0 || (_this$options$derefer7 = _this$options$derefer7.errors) === null || _this$options$derefer7 === void 0 || (_this$options$derefer8 = _this$options$derefer7.push) === null || _this$options$derefer8 === void 0 || _this$options$derefer8.call(_this$options$derefer7, wrappedError);
688
+ (_this$options$derefer13 = this.options.dereference.dereferenceOpts) === null || _this$options$derefer13 === void 0 || (_this$options$derefer13 = _this$options$derefer13.errors) === null || _this$options$derefer13 === void 0 || (_this$options$derefer14 = _this$options$derefer13.push) === null || _this$options$derefer14 === void 0 || _this$options$derefer14.call(_this$options$derefer13, wrappedError);
606
689
  return undefined;
607
690
  }
608
691
  }