Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 6f71707

Browse files
committed
fix(oas3): support dereferencing recursive references
1 parent 5f30229 commit 6f71707

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

packages/openapi3-parser/lib/parser/oas/parseReferenceObject.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,33 @@ const parseString = require('../parseString');
1212
const name = 'Reference Object';
1313
const requiredKeys = ['$ref'];
1414

15+
/**
16+
* Recursively dereference an element in the given component
17+
*
18+
* @param namespace {Namespace}
19+
* @param component {ObjectElement}
20+
* @param ref {StringElement}
21+
* @param element {Element}
22+
* @param parents {string[]} an optional collections of traversed parents
23+
*
24+
* @returns Element
25+
*/
26+
function dereference(namespace, component, ref, element, parents = []) {
27+
if (parents && parents.includes(element.element)) {
28+
// We've already cycled through this element. We're in a circular loop
29+
parents.shift();
30+
return createError(namespace, `Reference cannot be circular, '${ref.toValue()}' causes a circular reference via ${parents.join(', ')}`, ref);
31+
}
32+
33+
const match = component.get(element.element);
34+
if (match) {
35+
parents.push(element.element);
36+
return dereference(namespace, component, ref, match, parents);
37+
}
38+
39+
return element;
40+
}
41+
1542
/**
1643
* Parse Reference Object
1744
*
@@ -76,7 +103,7 @@ function parseReferenceObject(context, componentName, element, returnReferenceEl
76103
return new namespace.elements.ParseResult(
77104
component
78105
.filter((value, key) => key.toValue() === componentId && value)
79-
.map(value => value)
106+
.map(value => dereference(namespace, component, ref, value))
80107
);
81108
};
82109

packages/openapi3-parser/test/unit/parser/oas/parseReferenceObject-test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,29 @@ describe('Reference Object', () => {
8888
expect(parseResult.length).to.equal(0);
8989
});
9090

91+
it('can parse a reference to a reference', () => {
92+
const nodeAlias = new namespace.elements.Element();
93+
nodeAlias.element = 'Node';
94+
95+
context.state.components = new namespace.elements.Object({
96+
schemas: {
97+
Node: 'example',
98+
NodeAlias: nodeAlias,
99+
},
100+
});
101+
102+
const reference = new namespace.elements.Object({
103+
$ref: '#/components/schemas/NodeAlias',
104+
});
105+
const parseResult = parse(context, 'schemas', reference);
106+
107+
expect(parseResult.length).to.equal(1);
108+
const structure = parseResult.get(0);
109+
expect(structure).to.be.instanceof(namespace.elements.String);
110+
expect(structure.element).to.equal('string');
111+
expect(structure.toValue()).to.equal('example');
112+
});
113+
91114
describe('invalid references', () => {
92115
it('errors when parsing a non-components reference', () => {
93116
const reference = new namespace.elements.Object({
@@ -145,6 +168,32 @@ describe('Reference Object', () => {
145168

146169
expect(parseResult).to.contain.error("'#/components' is not defined");
147170
});
171+
172+
it('errors when parsing reference causing circular reference', () => {
173+
const createElement = (name) => {
174+
const element = new namespace.elements.Element();
175+
element.element = name;
176+
return element;
177+
};
178+
179+
context.state.components = new namespace.elements.Object({
180+
schemas: {
181+
A: createElement('B'),
182+
B: createElement('C'),
183+
C: createElement('D'),
184+
D: createElement('A'),
185+
},
186+
});
187+
188+
const reference = new namespace.elements.Object({
189+
$ref: '#/components/schemas/A',
190+
});
191+
192+
const parseResult = parse(context, 'schemas', reference);
193+
expect(parseResult).to.contain.error(
194+
"Reference cannot be circular, '#/components/schemas/A' causes a circular reference via C, D, A"
195+
);
196+
});
148197
});
149198

150199
describe('warnings for invalid properties', () => {

0 commit comments

Comments
 (0)