1 | "use strict";
|
2 |
|
3 | exports.__esModule = true;
|
4 | exports.default = void 0;
|
5 |
|
6 | var _crossFetch = require("cross-fetch");
|
7 |
|
8 | var _jsYaml = _interopRequireDefault(require("js-yaml"));
|
9 |
|
10 | var _querystringBrowser = _interopRequireDefault(require("querystring-browser"));
|
11 |
|
12 | var _url = _interopRequireDefault(require("url"));
|
13 |
|
14 | var _ = _interopRequireDefault(require("."));
|
15 |
|
16 | var _createError = _interopRequireDefault(require("./create-error"));
|
17 |
|
18 | var _helpers = require("../helpers");
|
19 |
|
20 | var _constants = require("../../constants");
|
21 |
|
22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
23 |
|
24 | const ABSOLUTE_URL_REGEXP = new RegExp('^([a-z]+://|//)', 'i');
|
25 | const JSONRefError = (0, _createError.default)('JSONRefError', function cb(message, extra, oriError) {
|
26 | this.originalError = oriError;
|
27 | Object.assign(this, extra || {});
|
28 | });
|
29 | const docCache = {};
|
30 | const specmapRefs = new WeakMap();
|
31 | const skipResolutionTestFns = [path =>
|
32 |
|
33 | path[0] === 'paths' && path[3] === 'responses' && path[5] === 'content' && path[7] === 'example', path =>
|
34 |
|
35 | path[0] === 'paths' && path[3] === 'requestBody' && path[4] === 'content' && path[6] === 'example'];
|
36 |
|
37 | const shouldSkipResolution = path => skipResolutionTestFns.some(fn => fn(path));
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | const plugin = {
|
63 | key: '$ref',
|
64 | plugin: (ref, key, fullPath, specmap) => {
|
65 | const specmapInstance = specmap.getInstance();
|
66 | const parent = fullPath.slice(0, -1);
|
67 |
|
68 | if ((0, _helpers.isFreelyNamed)(parent) || shouldSkipResolution(parent)) {
|
69 | return undefined;
|
70 | }
|
71 |
|
72 | const {
|
73 | baseDoc
|
74 | } = specmap.getContext(fullPath);
|
75 |
|
76 | if (typeof ref !== 'string') {
|
77 | return new JSONRefError('$ref: must be a string (JSON-Ref)', {
|
78 | $ref: ref,
|
79 | baseDoc,
|
80 | fullPath
|
81 | });
|
82 | }
|
83 |
|
84 | const splitString = split(ref);
|
85 | const refPath = splitString[0];
|
86 | const pointer = splitString[1] || '';
|
87 | let basePath;
|
88 |
|
89 | try {
|
90 | basePath = baseDoc || refPath ? absoluteify(refPath, baseDoc) : null;
|
91 | } catch (e) {
|
92 | return wrapError(e, {
|
93 | pointer,
|
94 | $ref: ref,
|
95 | basePath,
|
96 | fullPath
|
97 | });
|
98 | }
|
99 |
|
100 | let promOrVal;
|
101 | let tokens;
|
102 |
|
103 | if (pointerAlreadyInPath(pointer, basePath, parent, specmap)) {
|
104 |
|
105 |
|
106 |
|
107 |
|
108 | if (!specmapInstance.useCircularStructures) {
|
109 | const absolutifiedRef = (0, _helpers.absolutifyPointer)(ref, basePath);
|
110 |
|
111 | if (ref === absolutifiedRef) {
|
112 |
|
113 |
|
114 | return null;
|
115 | }
|
116 |
|
117 | return _.default.replace(fullPath, absolutifiedRef);
|
118 | }
|
119 | }
|
120 |
|
121 | if (basePath == null) {
|
122 | tokens = jsonPointerToArray(pointer);
|
123 | promOrVal = specmap.get(tokens);
|
124 |
|
125 | if (typeof promOrVal === 'undefined') {
|
126 | promOrVal = new JSONRefError(`Could not resolve reference: ${ref}`, {
|
127 | pointer,
|
128 | $ref: ref,
|
129 | baseDoc,
|
130 | fullPath
|
131 | });
|
132 | }
|
133 | } else {
|
134 | promOrVal = extractFromDoc(basePath, pointer);
|
135 |
|
136 | if (promOrVal.__value != null) {
|
137 | promOrVal = promOrVal.__value;
|
138 | } else {
|
139 | promOrVal = promOrVal.catch(e => {
|
140 | throw wrapError(e, {
|
141 | pointer,
|
142 | $ref: ref,
|
143 | baseDoc,
|
144 | fullPath
|
145 | });
|
146 | });
|
147 | }
|
148 | }
|
149 |
|
150 | if (promOrVal instanceof Error) {
|
151 | return [_.default.remove(fullPath), promOrVal];
|
152 | }
|
153 |
|
154 | const absolutifiedRef = (0, _helpers.absolutifyPointer)(ref, basePath);
|
155 |
|
156 | const patch = _.default.replace(parent, promOrVal, {
|
157 | $$ref: absolutifiedRef
|
158 | });
|
159 |
|
160 | if (basePath && basePath !== baseDoc) {
|
161 | return [patch, _.default.context(parent, {
|
162 | baseDoc: basePath
|
163 | })];
|
164 | }
|
165 |
|
166 | try {
|
167 |
|
168 |
|
169 | if (!patchValueAlreadyInPath(specmap.state, patch) || specmapInstance.useCircularStructures) {
|
170 | return patch;
|
171 | }
|
172 | } catch (e) {
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | return null;
|
182 | }
|
183 |
|
184 | return undefined;
|
185 | }
|
186 | };
|
187 | const mod = Object.assign(plugin, {
|
188 | docCache,
|
189 | absoluteify,
|
190 | clearCache,
|
191 | JSONRefError,
|
192 | wrapError,
|
193 | getDoc,
|
194 | split,
|
195 | extractFromDoc,
|
196 | fetchJSON,
|
197 | extract,
|
198 | jsonPointerToArray,
|
199 | unescapeJsonPointerToken
|
200 | });
|
201 | var _default = mod;
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 | exports.default = _default;
|
211 |
|
212 | function absoluteify(path, basePath) {
|
213 | if (!ABSOLUTE_URL_REGEXP.test(path)) {
|
214 | if (!basePath) {
|
215 | throw new JSONRefError(`Tried to resolve a relative URL, without having a basePath. path: '${path}' basePath: '${basePath}'`);
|
216 | }
|
217 |
|
218 | return _url.default.resolve(basePath, path);
|
219 | }
|
220 |
|
221 | return path;
|
222 | }
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | function wrapError(e, extra) {
|
233 | let message;
|
234 |
|
235 | if (e && e.response && e.response.body) {
|
236 | message = `${e.response.body.code} ${e.response.body.message}`;
|
237 | } else {
|
238 | message = e.message;
|
239 | }
|
240 |
|
241 | return new JSONRefError(`Could not resolve reference: ${message}`, extra, e);
|
242 | }
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 | function split(ref) {
|
250 | return (ref + '').split('#');
|
251 | }
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | function extractFromDoc(docPath, pointer) {
|
262 | const doc = docCache[docPath];
|
263 |
|
264 | if (doc && !_.default.isPromise(doc)) {
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | try {
|
272 | const v = extract(pointer, doc);
|
273 | return Object.assign(Promise.resolve(v), {
|
274 | __value: v
|
275 | });
|
276 | } catch (e) {
|
277 | return Promise.reject(e);
|
278 | }
|
279 | }
|
280 |
|
281 | return getDoc(docPath).then(_doc => extract(pointer, _doc));
|
282 | }
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | function clearCache(item) {
|
291 | if (typeof item !== 'undefined') {
|
292 | delete docCache[item];
|
293 | } else {
|
294 | Object.keys(docCache).forEach(key => {
|
295 | delete docCache[key];
|
296 | });
|
297 | }
|
298 | }
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | function getDoc(docPath) {
|
308 | const val = docCache[docPath];
|
309 |
|
310 | if (val) {
|
311 | return _.default.isPromise(val) ? val : Promise.resolve(val);
|
312 | }
|
313 |
|
314 |
|
315 |
|
316 | docCache[docPath] = mod.fetchJSON(docPath).then(doc => {
|
317 | docCache[docPath] = doc;
|
318 | return doc;
|
319 | });
|
320 | return docCache[docPath];
|
321 | }
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 | function fetchJSON(docPath) {
|
331 | return (0, _crossFetch.fetch)(docPath, {
|
332 | headers: {
|
333 | Accept: _constants.ACCEPT_HEADER_VALUE_FOR_DOCUMENTS
|
334 | },
|
335 | loadSpec: true
|
336 | }).then(res => res.text()).then(text => _jsYaml.default.safeLoad(text));
|
337 | }
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 | function extract(pointer, obj) {
|
348 | const tokens = jsonPointerToArray(pointer);
|
349 |
|
350 | if (tokens.length < 1) {
|
351 | return obj;
|
352 | }
|
353 |
|
354 | const val = _.default.getIn(obj, tokens);
|
355 |
|
356 | if (typeof val === 'undefined') {
|
357 | throw new JSONRefError(`Could not resolve pointer: ${pointer} does not exist in document`, {
|
358 | pointer
|
359 | });
|
360 | }
|
361 |
|
362 | return val;
|
363 | }
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 | function jsonPointerToArray(pointer) {
|
371 | if (typeof pointer !== 'string') {
|
372 | throw new TypeError(`Expected a string, got a ${typeof pointer}`);
|
373 | }
|
374 |
|
375 | if (pointer[0] === '/') {
|
376 | pointer = pointer.substr(1);
|
377 | }
|
378 |
|
379 | if (pointer === '') {
|
380 | return [];
|
381 | }
|
382 |
|
383 | return pointer.split('/').map(unescapeJsonPointerToken);
|
384 | }
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 | function unescapeJsonPointerToken(token) {
|
392 | if (typeof token !== 'string') {
|
393 | return token;
|
394 | }
|
395 |
|
396 | return _querystringBrowser.default.unescape(token.replace(/~1/g, '/').replace(/~0/g, '~'));
|
397 | }
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 | function escapeJsonPointerToken(token) {
|
405 | return _querystringBrowser.default.escape(token.replace(/~/g, '~0').replace(/\//g, '~1'));
|
406 | }
|
407 |
|
408 | function arrayToJsonPointer(arr) {
|
409 | if (arr.length === 0) {
|
410 | return '';
|
411 | }
|
412 |
|
413 | return `/${arr.map(escapeJsonPointerToken).join('/')}`;
|
414 | }
|
415 |
|
416 | const pointerBoundaryChar = c => !c || c === '/' || c === '#';
|
417 |
|
418 | function pointerIsAParent(pointer, parentPointer) {
|
419 | if (pointerBoundaryChar(parentPointer)) {
|
420 |
|
421 | return true;
|
422 | }
|
423 |
|
424 | const nextChar = pointer.charAt(parentPointer.length);
|
425 | const lastParentChar = parentPointer.slice(-1);
|
426 | return pointer.indexOf(parentPointer) === 0 && (!nextChar || nextChar === '/' || nextChar === '#') && lastParentChar !== '#';
|
427 | }
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 | function pointerAlreadyInPath(pointer, basePath, parent, specmap) {
|
437 | let refs = specmapRefs.get(specmap);
|
438 |
|
439 | if (!refs) {
|
440 |
|
441 |
|
442 | refs = {};
|
443 | specmapRefs.set(specmap, refs);
|
444 | }
|
445 |
|
446 | const parentPointer = arrayToJsonPointer(parent);
|
447 | const fullyQualifiedPointer = `${basePath || '<specmap-base>'}#${pointer}`;
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 | const safeParentPointer = parentPointer.replace(/allOf\/\d+\/?/g, '');
|
459 |
|
460 |
|
461 |
|
462 | const rootDoc = specmap.contextTree.get([]).baseDoc;
|
463 |
|
464 | if (basePath == rootDoc && pointerIsAParent(safeParentPointer, pointer)) {
|
465 |
|
466 | return true;
|
467 | }
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 |
|
474 | let currPath = '';
|
475 | const hasIndirectCycle = parent.some(token => {
|
476 | currPath = `${currPath}/${escapeJsonPointerToken(token)}`;
|
477 | return refs[currPath] && refs[currPath].some(ref => {
|
478 | return pointerIsAParent(ref, fullyQualifiedPointer) || pointerIsAParent(fullyQualifiedPointer, ref);
|
479 | });
|
480 | });
|
481 |
|
482 | if (hasIndirectCycle) {
|
483 | return true;
|
484 | }
|
485 |
|
486 |
|
487 |
|
488 | refs[safeParentPointer] = (refs[safeParentPointer] || []).concat(fullyQualifiedPointer);
|
489 | return undefined;
|
490 | }
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 | function patchValueAlreadyInPath(root, patch) {
|
497 | const ancestors = [root];
|
498 | patch.path.reduce((parent, p) => {
|
499 | ancestors.push(parent[p]);
|
500 | return parent[p];
|
501 | }, root);
|
502 | return pointToAncestor(patch.value);
|
503 |
|
504 | function pointToAncestor(obj) {
|
505 | return _.default.isObject(obj) && (ancestors.indexOf(obj) >= 0 || Object.keys(obj).some(k => {
|
506 | return pointToAncestor(obj[k]);
|
507 | }));
|
508 | }
|
509 | } |
\ | No newline at end of file |