1 | ;
|
2 |
|
3 | var $Ref = require('./ref'),
|
4 | Pointer = require('./pointer'),
|
5 | ono = require('ono'),
|
6 | url = require('./util/url');
|
7 |
|
8 | module.exports = dereference;
|
9 |
|
10 | /**
|
11 | * Crawls the JSON schema, finds all JSON references, and dereferences them.
|
12 | * This method mutates the JSON schema object, replacing JSON references with their resolved value.
|
13 | *
|
14 | * @param {$RefParser} parser
|
15 | * @param {$RefParserOptions} options
|
16 | */
|
17 | function dereference (parser, options) {
|
18 | // console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
|
19 | var dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, '#', [], parser.$refs, options);
|
20 | parser.$refs.circular = dereferenced.circular;
|
21 | parser.schema = dereferenced.value;
|
22 | }
|
23 |
|
24 | /**
|
25 | * Recursively crawls the given value, and dereferences any JSON references.
|
26 | *
|
27 | * @param {*} obj - The value to crawl. If it's not an object or array, it will be ignored.
|
28 | * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash
|
29 | * @param {string} pathFromRoot - The path of `obj` from the schema root
|
30 | * @param {object[]} parents - An array of the parent objects that have already been dereferenced
|
31 | * @param {$Refs} $refs
|
32 | * @param {$RefParserOptions} options
|
33 | * @returns {{value: object, circular: boolean}}
|
34 | */
|
35 | function crawl (obj, path, pathFromRoot, parents, $refs, options) {
|
36 | var dereferenced;
|
37 | var result = {
|
38 | value: obj,
|
39 | circular: false
|
40 | };
|
41 |
|
42 | if (obj && typeof obj === 'object') {
|
43 | parents.push(obj);
|
44 |
|
45 | if ($Ref.isAllowed$Ref(obj, options)) {
|
46 | dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, $refs, options);
|
47 | result.circular = dereferenced.circular;
|
48 | result.value = dereferenced.value;
|
49 | }
|
50 | else {
|
51 | Object.keys(obj).forEach(function (key) {
|
52 | var keyPath = Pointer.join(path, key);
|
53 | var keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
54 | var value = obj[key];
|
55 | var circular = false;
|
56 |
|
57 | if ($Ref.isAllowed$Ref(value, options)) {
|
58 | dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, $refs, options);
|
59 | circular = dereferenced.circular;
|
60 | obj[key] = dereferenced.value;
|
61 | }
|
62 | else {
|
63 | if (parents.indexOf(value) === -1) {
|
64 | dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, $refs, options);
|
65 | circular = dereferenced.circular;
|
66 | obj[key] = dereferenced.value;
|
67 | }
|
68 | else {
|
69 | circular = foundCircularReference(keyPath, $refs, options);
|
70 | }
|
71 | }
|
72 |
|
73 | // Set the "isCircular" flag if this or any other property is circular
|
74 | result.circular = result.circular || circular;
|
75 | });
|
76 | }
|
77 |
|
78 | parents.pop();
|
79 | }
|
80 |
|
81 | return result;
|
82 | }
|
83 |
|
84 | /**
|
85 | * Dereferences the given JSON Reference, and then crawls the resulting value.
|
86 | *
|
87 | * @param {{$ref: string}} $ref - The JSON Reference to resolve
|
88 | * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash
|
89 | * @param {string} pathFromRoot - The path of `$ref` from the schema root
|
90 | * @param {object[]} parents - An array of the parent objects that have already been dereferenced
|
91 | * @param {$Refs} $refs
|
92 | * @param {$RefParserOptions} options
|
93 | * @returns {{value: object, circular: boolean}}
|
94 | */
|
95 | function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) {
|
96 | // console.log('Dereferencing $ref pointer "%s" at %s', $ref.$ref, path);
|
97 |
|
98 | var $refPath = url.resolve(path, $ref.$ref);
|
99 | var pointer = $refs._resolve($refPath, options);
|
100 |
|
101 | // Check for circular references
|
102 | var directCircular = pointer.circular;
|
103 | var circular = directCircular || parents.indexOf(pointer.value) !== -1;
|
104 | circular && foundCircularReference(path, $refs, options);
|
105 |
|
106 | // Dereference the JSON reference
|
107 | var dereferencedValue = $Ref.dereference($ref, pointer.value);
|
108 |
|
109 | // Crawl the dereferenced value (unless it's circular)
|
110 | if (!circular) {
|
111 | // Determine if the dereferenced value is circular
|
112 | var dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, $refs, options);
|
113 | circular = dereferenced.circular;
|
114 | dereferencedValue = dereferenced.value;
|
115 | }
|
116 |
|
117 | if (circular && !directCircular && options.dereference.circular === 'ignore') {
|
118 | // The user has chosen to "ignore" circular references, so don't change the value
|
119 | dereferencedValue = $ref;
|
120 | }
|
121 |
|
122 | if (directCircular) {
|
123 | // The pointer is a DIRECT circular reference (i.e. it references itself).
|
124 | // So replace the $ref path with the absolute path from the JSON Schema root
|
125 | dereferencedValue.$ref = pathFromRoot;
|
126 | }
|
127 |
|
128 | return {
|
129 | circular: circular,
|
130 | value: dereferencedValue
|
131 | };
|
132 | }
|
133 |
|
134 | /**
|
135 | * Called when a circular reference is found.
|
136 | * It sets the {@link $Refs#circular} flag, and throws an error if options.dereference.circular is false.
|
137 | *
|
138 | * @param {string} keyPath - The JSON Reference path of the circular reference
|
139 | * @param {$Refs} $refs
|
140 | * @param {$RefParserOptions} options
|
141 | * @returns {boolean} - always returns true, to indicate that a circular reference was found
|
142 | */
|
143 | function foundCircularReference (keyPath, $refs, options) {
|
144 | $refs.circular = true;
|
145 | if (!options.dereference.circular) {
|
146 | throw ono.reference('Circular $ref pointer found at %s', keyPath);
|
147 | }
|
148 | return true;
|
149 | }
|