Skip to content

Commit 9360891

Browse files
authored
Fix cyclic ref problem when baseDoc is specified (#1038)
1 parent 788039a commit 9360891

File tree

3 files changed

+67
-10
lines changed

3 files changed

+67
-10
lines changed

src/resolver.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ export default function resolve({http, fetch, spec, url, baseDoc, mode, allowMet
3838
return doResolve(spec)
3939

4040
function doResolve(_spec) {
41-
plugins.refs.docCache[baseDoc] = _spec
41+
if (baseDoc) {
42+
plugins.refs.docCache[baseDoc] = _spec
43+
}
4244

4345
// Build a json-fetcher ( ie: give it a URL and get json out )
4446
plugins.refs.fetchJSON = makeFetchJSON(http)

src/specmap/lib/refs.js

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,20 @@ const plugin = {
8989
}
9090
}
9191
else {
92-
promOrVal = extractFromDoc(basePath, pointer).catch((e) => {
93-
throw wrapError(e, {
94-
pointer,
95-
$ref: ref,
96-
baseDoc,
97-
fullPath,
92+
promOrVal = extractFromDoc(basePath, pointer)
93+
if (promOrVal.__value != null) {
94+
promOrVal = promOrVal.__value
95+
}
96+
else {
97+
promOrVal = promOrVal.catch((e) => {
98+
throw wrapError(e, {
99+
pointer,
100+
$ref: ref,
101+
baseDoc,
102+
fullPath,
103+
})
98104
})
99-
})
105+
}
100106
}
101107

102108
if (promOrVal instanceof Error) {
@@ -178,7 +184,24 @@ function split(ref) {
178184
* @api public
179185
*/
180186
function extractFromDoc(docPath, pointer) {
181-
return getDoc(docPath).then(doc => extract(pointer, doc))
187+
const doc = docCache[docPath]
188+
if (doc && !lib.isPromise(doc)) {
189+
// If doc is already available, return __value together with the promise.
190+
// __value is for special handling in cycle check:
191+
// pointerAlreadyInPath() won't work if patch.value is a promise,
192+
// thus when that promise is finally resolved, cycle might happen (because
193+
// `spec` and `docCache[basePath]` refer to the exact same object).
194+
// See test "should resolve a cyclic spec when baseDoc is specified".
195+
try {
196+
const v = extract(pointer, doc)
197+
return Object.assign(Promise.resolve(v), {__value: v})
198+
}
199+
catch (e) {
200+
return Promise.reject(e)
201+
}
202+
}
203+
204+
return getDoc(docPath).then(_doc => extract(pointer, _doc))
182205
}
183206

184207
/**
@@ -331,7 +354,7 @@ function pointerAlreadyInPath(pointer, basePath, parent, specmap) {
331354
// Detect by checking that the parent path doesn't start with pointer.
332355
// This only applies if the pointer is internal, i.e. basePath === rootPath (could be null)
333356
const rootDoc = specmap.contextTree.get([]).baseDoc
334-
if (basePath === rootDoc && pointerIsAParent(parentPointer, pointer)) {
357+
if (basePath == rootDoc && pointerIsAParent(parentPointer, pointer)) { // eslint-disable-line
335358
return true
336359
}
337360

test/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ describe('constructor', () => {
5252
})
5353
})
5454

55+
it('should resolve a cyclic spec when baseDoc is specified', function (cb) {
56+
const spec = {
57+
paths: {
58+
post: {
59+
parameters: [
60+
{
61+
$ref: '#/definitions/list',
62+
}
63+
]
64+
}
65+
},
66+
definitions: {
67+
item: {
68+
items: {
69+
$ref: '#/definitions/item'
70+
}
71+
},
72+
list: {
73+
items: {
74+
$ref: '#/definitions/item'
75+
}
76+
}
77+
}
78+
}
79+
80+
Swagger.resolve({spec, baseDoc: 'http://whatever/'}).then((swag) => {
81+
expect(swag.errors).toEqual([])
82+
cb()
83+
})
84+
})
85+
86+
5587
it('should keep resolve errors in #errors', function () {
5688
// Given
5789
const spec = {

0 commit comments

Comments
 (0)