Skip to content

Commit 8435ee1

Browse files
authored
fix(reference): fix regression in nested paths dereferencing (#3960)
Refs #3959
1 parent 0e5e8ad commit 8435ee1

File tree

9 files changed

+176
-34
lines changed

9 files changed

+176
-34
lines changed

packages/apidom-reference/src/dereference/strategies/asyncapi-2/visitor.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ const AsyncApi2DereferenceVisitor = stampit({
130130
path: (string | number)[],
131131
ancestors: [Element | Element[]],
132132
) {
133+
// skip current referencing element as it's already been access
134+
if (this.indirections.includes(referencingElement)) {
135+
return false;
136+
}
137+
133138
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
134139

135140
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -228,10 +233,10 @@ const AsyncApi2DereferenceVisitor = stampit({
228233
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
229234
*/
230235
if (
231-
!ancestorsLineage.includesCycle(referencedElement) &&
232236
(isExternalReference ||
233237
isReferenceElement(referencedElement) ||
234-
['error', 'replace'].includes(this.options.dereference.circular))
238+
['error', 'replace'].includes(this.options.dereference.circular)) &&
239+
!ancestorsLineage.includesCycle(referencedElement)
235240
) {
236241
// append referencing reference to ancestors lineage
237242
directAncestors.add(referencingElement);
@@ -326,6 +331,11 @@ const AsyncApi2DereferenceVisitor = stampit({
326331
return undefined;
327332
}
328333

334+
// skip current referencing element as it's already been access
335+
if (this.indirections.includes(referencingElement)) {
336+
return false;
337+
}
338+
329339
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
330340

331341
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -416,10 +426,10 @@ const AsyncApi2DereferenceVisitor = stampit({
416426
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
417427
*/
418428
if (
419-
!ancestorsLineage.includesCycle(referencedElement) &&
420429
(isExternalReference ||
421430
(isChannelItemElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
422-
['error', 'replace'].includes(this.options.dereference.circular))
431+
['error', 'replace'].includes(this.options.dereference.circular)) &&
432+
!ancestorsLineage.includesCycle(referencedElement)
423433
) {
424434
// append referencing reference to ancestors lineage
425435
directAncestors.add(referencingElement);

packages/apidom-reference/src/dereference/strategies/openapi-2/visitor.ts

+21-6
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ const OpenApi2DereferenceVisitor = stampit({
131131
path: (string | number)[],
132132
ancestors: [Element | Element[]],
133133
) {
134+
// skip current referencing element as it's already been access
135+
if (this.indirections.includes(referencingElement)) {
136+
return false;
137+
}
138+
134139
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
135140

136141
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -229,10 +234,10 @@ const OpenApi2DereferenceVisitor = stampit({
229234
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
230235
*/
231236
if (
232-
!ancestorsLineage.includesCycle(referencedElement) &&
233237
(isExternalReference ||
234238
isReferenceElement(referencedElement) ||
235-
['error', 'replace'].includes(this.options.dereference.circular))
239+
['error', 'replace'].includes(this.options.dereference.circular)) &&
240+
!ancestorsLineage.includesCycle(referencedElement)
236241
) {
237242
// append referencing reference to ancestors lineage
238243
directAncestors.add(referencingElement);
@@ -302,6 +307,11 @@ const OpenApi2DereferenceVisitor = stampit({
302307
return undefined;
303308
}
304309

310+
// skip current referencing element as it's already been access
311+
if (this.indirections.includes(referencingElement)) {
312+
return false;
313+
}
314+
305315
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
306316

307317
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -392,10 +402,10 @@ const OpenApi2DereferenceVisitor = stampit({
392402
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
393403
*/
394404
if (
395-
!ancestorsLineage.includesCycle(referencedElement) &&
396405
(isExternalReference ||
397406
(isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
398-
['error', 'replace'].includes(this.options.dereference.circular))
407+
['error', 'replace'].includes(this.options.dereference.circular)) &&
408+
!ancestorsLineage.includesCycle(referencedElement)
399409
) {
400410
// append referencing reference to ancestors lineage
401411
directAncestors.add(referencingElement);
@@ -472,6 +482,11 @@ const OpenApi2DereferenceVisitor = stampit({
472482
path: (string | number)[],
473483
ancestors: [Element | Element[]],
474484
) {
485+
// skip current referencing element as it's already been access
486+
if (this.indirections.includes(referencingElement)) {
487+
return false;
488+
}
489+
475490
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
476491

477492
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -570,10 +585,10 @@ const OpenApi2DereferenceVisitor = stampit({
570585
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
571586
*/
572587
if (
573-
!ancestorsLineage.includesCycle(referencedElement) &&
574588
(isExternalReference ||
575589
isJSONReferenceElement(referencedElement) ||
576-
['error', 'replace'].includes(this.options.dereference.circular))
590+
['error', 'replace'].includes(this.options.dereference.circular)) &&
591+
!ancestorsLineage.includesCycle(referencedElement)
577592
) {
578593
// append referencing reference to ancestors lineage
579594
directAncestors.add(referencingElement);

packages/apidom-reference/src/dereference/strategies/openapi-3-0/visitor.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ const OpenApi3_0DereferenceVisitor = stampit({
135135
path: (string | number)[],
136136
ancestors: [Element | Element[]],
137137
) {
138+
// skip current referencing element as it's already been access
139+
if (this.indirections.includes(referencingElement)) {
140+
return false;
141+
}
142+
138143
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
139144

140145
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -233,10 +238,10 @@ const OpenApi3_0DereferenceVisitor = stampit({
233238
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
234239
*/
235240
if (
236-
!ancestorsLineage.includesCycle(referencedElement) &&
237241
(isExternalReference ||
238242
isReferenceElement(referencedElement) ||
239-
['error', 'replace'].includes(this.options.dereference.circular))
243+
['error', 'replace'].includes(this.options.dereference.circular)) &&
244+
!ancestorsLineage.includesCycle(referencedElement)
240245
) {
241246
// append referencing reference to ancestors lineage
242247
directAncestors.add(referencingElement);
@@ -305,6 +310,11 @@ const OpenApi3_0DereferenceVisitor = stampit({
305310
return undefined;
306311
}
307312

313+
// skip current referencing element as it's already been access
314+
if (this.indirections.includes(referencingElement)) {
315+
return false;
316+
}
317+
308318
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
309319

310320
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -395,10 +405,10 @@ const OpenApi3_0DereferenceVisitor = stampit({
395405
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
396406
*/
397407
if (
398-
!ancestorsLineage.includesCycle(referencedElement) &&
399408
(isExternalReference ||
400409
(isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
401-
['error', 'replace'].includes(this.options.dereference.circular))
410+
['error', 'replace'].includes(this.options.dereference.circular)) &&
411+
!ancestorsLineage.includesCycle(referencedElement)
402412
) {
403413
// append referencing reference to ancestors lineage
404414
directAncestors.add(referencingElement);

packages/apidom-reference/src/dereference/strategies/openapi-3-1/visitor.ts

+43-19
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ const OpenApi3_1DereferenceVisitor = stampit({
145145
path: (string | number)[],
146146
ancestors: [Element | Element[]],
147147
) {
148+
// skip current referencing element as it's already been access
149+
if (this.indirections.includes(referencingElement)) {
150+
return false;
151+
}
152+
148153
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
149154

150155
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -241,10 +246,10 @@ const OpenApi3_1DereferenceVisitor = stampit({
241246
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
242247
*/
243248
if (
244-
!ancestorsLineage.includesCycle(referencedElement) &&
245249
(isExternalReference ||
246250
isReferenceElement(referencedElement) ||
247-
['error', 'replace'].includes(this.options.dereference.circular))
251+
['error', 'replace'].includes(this.options.dereference.circular)) &&
252+
!ancestorsLineage.includesCycle(referencedElement)
248253
) {
249254
// append referencing reference to ancestors lineage
250255
directAncestors.add(referencingElement);
@@ -329,6 +334,11 @@ const OpenApi3_1DereferenceVisitor = stampit({
329334
return undefined;
330335
}
331336

337+
// skip current referencing element as it's already been access
338+
if (this.indirections.includes(referencingElement)) {
339+
return false;
340+
}
341+
332342
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
333343

334344
const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
@@ -419,10 +429,10 @@ const OpenApi3_1DereferenceVisitor = stampit({
419429
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
420430
*/
421431
if (
422-
!ancestorsLineage.includesCycle(referencedElement) &&
423432
(isExternalReference ||
424433
(isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
425-
['error', 'replace'].includes(this.options.dereference.circular))
434+
['error', 'replace'].includes(this.options.dereference.circular)) &&
435+
!ancestorsLineage.includesCycle(referencedElement)
426436
) {
427437
// append referencing reference to ancestors lineage
428438
directAncestors.add(referencingElement);
@@ -667,18 +677,23 @@ const OpenApi3_1DereferenceVisitor = stampit({
667677
return undefined;
668678
}
669679

680+
// skip current referencing element as it's already been access
681+
if (this.indirections.includes(referencingElement)) {
682+
return false;
683+
}
684+
670685
const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
671686

672687
// compute baseURI using rules around $id and $ref keywords
673688
let reference = await this.toReference(url.unsanitize(this.reference.uri));
674689
let { uri: retrievalURI } = reference;
675-
const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement) as string;
690+
const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement)!;
676691
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
677692
const file = File({ uri: $refBaseURIStrippedHash });
678693
const isUnknownURI = none((r: IResolver) => r.canRead(file), this.options.resolve.resolvers);
679694
const isURL = !isUnknownURI;
680-
const isInternalReference = (uri: string) => url.stripHash(this.reference.uri) === uri;
681-
const isExternalReference = (uri: string) => !isInternalReference(uri);
695+
let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI;
696+
let isExternalReference = !isInternalReference;
682697

683698
this.indirections.push(referencingElement);
684699

@@ -689,29 +704,35 @@ const OpenApi3_1DereferenceVisitor = stampit({
689704
if (isUnknownURI || isURL) {
690705
// we're dealing with canonical URI or URL with possible fragment
691706
retrievalURI = this.toBaseURI($refBaseURI);
692-
693707
const selector = $refBaseURI;
694708
const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
695709
referencedElement = uriEvaluate(selector, referenceAsSchema);
696710
referencedElement = maybeRefractToSchemaElement(referencedElement);
697711
referencedElement.id = identityManager.identify(referencedElement);
698712

699713
// ignore resolving internal Schema Objects
700-
if (!this.options.resolve.internal) {
714+
if (!this.options.resolve.internal && isInternalReference) {
715+
// skip traversing this schema element but traverse all it's child elements
716+
return undefined;
717+
}
718+
// ignore resolving external Schema Objects
719+
if (!this.options.resolve.external && isExternalReference) {
701720
// skip traversing this schema element but traverse all it's child elements
702721
return undefined;
703722
}
704723
} else {
705724
// we're assuming here that we're dealing with JSON Pointer here
706725
retrievalURI = this.toBaseURI($refBaseURI);
726+
isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
727+
isExternalReference = !isInternalReference;
707728

708729
// ignore resolving internal Schema Objects
709-
if (!this.options.resolve.internal && isInternalReference(retrievalURI)) {
730+
if (!this.options.resolve.internal && isInternalReference) {
710731
// skip traversing this schema element but traverse all it's child elements
711732
return undefined;
712733
}
713734
// ignore resolving external Schema Objects
714-
if (!this.options.resolve.external && isExternalReference(retrievalURI)) {
735+
if (!this.options.resolve.external && isExternalReference) {
715736
// skip traversing this schema element but traverse all it's child elements
716737
return undefined;
717738
}
@@ -731,15 +752,16 @@ const OpenApi3_1DereferenceVisitor = stampit({
731752
if (isURL && error instanceof EvaluationJsonSchemaUriError) {
732753
if (isAnchor(uriToAnchor($refBaseURI))) {
733754
// we're dealing with JSON Schema $anchor here
734-
retrievalURI = this.toBaseURI($refBaseURI);
755+
isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
756+
isExternalReference = !isInternalReference;
735757

736758
// ignore resolving internal Schema Objects
737-
if (!this.options.resolve.internal && isInternalReference(retrievalURI)) {
759+
if (!this.options.resolve.internal && isInternalReference) {
738760
// skip traversing this schema element but traverse all it's child elements
739761
return undefined;
740762
}
741763
// ignore resolving external Schema Objects
742-
if (!this.options.resolve.external && isExternalReference(retrievalURI)) {
764+
if (!this.options.resolve.external && isExternalReference) {
743765
// skip traversing this schema element but traverse all it's child elements
744766
return undefined;
745767
}
@@ -753,14 +775,16 @@ const OpenApi3_1DereferenceVisitor = stampit({
753775
} else {
754776
// we're assuming here that we're dealing with JSON Pointer here
755777
retrievalURI = this.toBaseURI($refBaseURI);
778+
isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
779+
isExternalReference = !isInternalReference;
756780

757781
// ignore resolving internal Schema Objects
758-
if (!this.options.resolve.internal && isInternalReference(retrievalURI)) {
782+
if (!this.options.resolve.internal && isInternalReference) {
759783
// skip traversing this schema element but traverse all it's child elements
760784
return undefined;
761785
}
762786
// ignore resolving external Schema Objects
763-
if (!this.options.resolve.external && isExternalReference(retrievalURI)) {
787+
if (!this.options.resolve.external && isExternalReference) {
764788
// skip traversing this schema element but traverse all it's child elements
765789
return undefined;
766790
}
@@ -825,10 +849,10 @@ const OpenApi3_1DereferenceVisitor = stampit({
825849
* 3. We are dereferencing the fragment lazily/eagerly depending on circular mode
826850
*/
827851
if (
828-
!ancestorsLineage.includesCycle(referencedElement) &&
829-
(isExternalReference(retrievalURI) ||
852+
(isExternalReference ||
830853
(isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
831-
['error', 'replace'].includes(this.options.dereference.circular))
854+
['error', 'replace'].includes(this.options.dereference.circular)) &&
855+
!ancestorsLineage.includesCycle(referencedElement)
832856
) {
833857
// append referencing reference to ancestors lineage
834858
directAncestors.add(referencingElement);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[
2+
{
3+
openapi: '3.1.0',
4+
components: {
5+
schemas: {
6+
User: {
7+
type: 'object',
8+
properties: <ref *1> {
9+
login: { type: 'string' },
10+
password: { type: 'string' },
11+
profile: {
12+
type: 'object',
13+
properties: {
14+
avatar: { type: 'string' },
15+
user: { type: 'object', properties: [Circular *1] }
16+
},
17+
'$id': './nested/'
18+
}
19+
}
20+
}
21+
}
22+
}
23+
}
24+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$defs": {
3+
"UserProfile": {
4+
"$id": "./nested/",
5+
"$ref": "./ex.json"
6+
}
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"avatar": {
5+
"type": "string"
6+
},
7+
"user": {
8+
"$ref": "../../root.json#/components/schemas/User"
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)