1 | ;
|
2 |
|
3 | module.exports = $Ref;
|
4 |
|
5 | var Pointer = require('./pointer');
|
6 |
|
7 | /**
|
8 | * This class represents a single JSON reference and its resolved value.
|
9 | *
|
10 | * @constructor
|
11 | */
|
12 | function $Ref () {
|
13 | /**
|
14 | * The file path or URL of the referenced file.
|
15 | * This path is relative to the path of the main JSON schema file.
|
16 | *
|
17 | * This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file.
|
18 | * Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get
|
19 | * specific JSON pointers within the file.
|
20 | *
|
21 | * @type {string}
|
22 | */
|
23 | this.path = undefined;
|
24 |
|
25 | /**
|
26 | * The resolved value of the JSON reference.
|
27 | * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
|
28 | * @type {?*}
|
29 | */
|
30 | this.value = undefined;
|
31 |
|
32 | /**
|
33 | * The {@link $Refs} object that contains this {@link $Ref} object.
|
34 | * @type {$Refs}
|
35 | */
|
36 | this.$refs = undefined;
|
37 |
|
38 | /**
|
39 | * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
|
40 | * @type {?string}
|
41 | */
|
42 | this.pathType = undefined;
|
43 | }
|
44 |
|
45 | /**
|
46 | * Determines whether the given JSON reference exists within this {@link $Ref#value}.
|
47 | *
|
48 | * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash
|
49 | * @param {$RefParserOptions} options
|
50 | * @returns {boolean}
|
51 | */
|
52 | $Ref.prototype.exists = function (path, options) {
|
53 | try {
|
54 | this.resolve(path, options);
|
55 | return true;
|
56 | }
|
57 | catch (e) {
|
58 | return false;
|
59 | }
|
60 | };
|
61 |
|
62 | /**
|
63 | * Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value.
|
64 | *
|
65 | * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash
|
66 | * @param {$RefParserOptions} options
|
67 | * @returns {*} - Returns the resolved value
|
68 | */
|
69 | $Ref.prototype.get = function (path, options) {
|
70 | return this.resolve(path, options).value;
|
71 | };
|
72 |
|
73 | /**
|
74 | * Resolves the given JSON reference within this {@link $Ref#value}.
|
75 | *
|
76 | * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash
|
77 | * @param {$RefParserOptions} options
|
78 | * @param {string} [friendlyPath] - The original user-specified path (used for error messages)
|
79 | * @returns {Pointer}
|
80 | */
|
81 | $Ref.prototype.resolve = function (path, options, friendlyPath) {
|
82 | var pointer = new Pointer(this, path, friendlyPath);
|
83 | return pointer.resolve(this.value, options);
|
84 | };
|
85 |
|
86 | /**
|
87 | * Sets the value of a nested property within this {@link $Ref#value}.
|
88 | * If the property, or any of its parents don't exist, they will be created.
|
89 | *
|
90 | * @param {string} path - The full path of the property to set, optionally with a JSON pointer in the hash
|
91 | * @param {*} value - The value to assign
|
92 | */
|
93 | $Ref.prototype.set = function (path, value) {
|
94 | var pointer = new Pointer(this, path);
|
95 | this.value = pointer.set(this.value, value);
|
96 | };
|
97 |
|
98 | /**
|
99 | * Determines whether the given value is a JSON reference.
|
100 | *
|
101 | * @param {*} value - The value to inspect
|
102 | * @returns {boolean}
|
103 | */
|
104 | $Ref.is$Ref = function (value) {
|
105 | return value && typeof value === 'object' && typeof value.$ref === 'string' && value.$ref.length > 0;
|
106 | };
|
107 |
|
108 | /**
|
109 | * Determines whether the given value is an external JSON reference.
|
110 | *
|
111 | * @param {*} value - The value to inspect
|
112 | * @returns {boolean}
|
113 | */
|
114 | $Ref.isExternal$Ref = function (value) {
|
115 | return $Ref.is$Ref(value) && value.$ref[0] !== '#';
|
116 | };
|
117 |
|
118 | /**
|
119 | * Determines whether the given value is a JSON reference, and whether it is allowed by the options.
|
120 | * For example, if it references an external file, then options.resolve.external must be true.
|
121 | *
|
122 | * @param {*} value - The value to inspect
|
123 | * @param {$RefParserOptions} options
|
124 | * @returns {boolean}
|
125 | */
|
126 | $Ref.isAllowed$Ref = function (value, options) {
|
127 | if ($Ref.is$Ref(value)) {
|
128 | if (value.$ref.substr(0, 2) === '#/' || value.$ref === '#') {
|
129 | // It's a JSON Pointer reference, which is always allowed
|
130 | return true;
|
131 | }
|
132 | else if (value.$ref[0] !== '#' && (!options || options.resolve.external)) {
|
133 | // It's an external reference, which is allowed by the options
|
134 | return true;
|
135 | }
|
136 | }
|
137 | };
|
138 |
|
139 | /**
|
140 | * Determines whether the given value is a JSON reference that "extends" its resolved value.
|
141 | * That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
|
142 | * an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved
|
143 | * value, plus the extra properties.
|
144 | *
|
145 | * @example:
|
146 | * {
|
147 | * person: {
|
148 | * properties: {
|
149 | * firstName: { type: string }
|
150 | * lastName: { type: string }
|
151 | * }
|
152 | * }
|
153 | * employee: {
|
154 | * properties: {
|
155 | * $ref: #/person/properties
|
156 | * salary: { type: number }
|
157 | * }
|
158 | * }
|
159 | * }
|
160 | *
|
161 | * In this example, "employee" is an extended $ref, since it extends "person" with an additional
|
162 | * property (salary). The result is a NEW value that looks like this:
|
163 | *
|
164 | * {
|
165 | * properties: {
|
166 | * firstName: { type: string }
|
167 | * lastName: { type: string }
|
168 | * salary: { type: number }
|
169 | * }
|
170 | * }
|
171 | *
|
172 | * @param {*} value - The value to inspect
|
173 | * @returns {boolean}
|
174 | */
|
175 | $Ref.isExtended$Ref = function (value) {
|
176 | return $Ref.is$Ref(value) && Object.keys(value).length > 1;
|
177 | };
|
178 |
|
179 | /**
|
180 | * Returns the resolved value of a JSON Reference.
|
181 | * If necessary, the resolved value is merged with the JSON Reference to create a new object
|
182 | *
|
183 | * @example:
|
184 | * {
|
185 | * person: {
|
186 | * properties: {
|
187 | * firstName: { type: string }
|
188 | * lastName: { type: string }
|
189 | * }
|
190 | * }
|
191 | * employee: {
|
192 | * properties: {
|
193 | * $ref: #/person/properties
|
194 | * salary: { type: number }
|
195 | * }
|
196 | * }
|
197 | * }
|
198 | *
|
199 | * When "person" and "employee" are merged, you end up with the following object:
|
200 | *
|
201 | * {
|
202 | * properties: {
|
203 | * firstName: { type: string }
|
204 | * lastName: { type: string }
|
205 | * salary: { type: number }
|
206 | * }
|
207 | * }
|
208 | *
|
209 | * @param {object} $ref - The JSON reference object (the one with the "$ref" property)
|
210 | * @param {*} resolvedValue - The resolved value, which can be any type
|
211 | * @returns {*} - Returns the dereferenced value
|
212 | */
|
213 | $Ref.dereference = function ($ref, resolvedValue) {
|
214 | if (resolvedValue && typeof resolvedValue === 'object' && $Ref.isExtended$Ref($ref)) {
|
215 | var merged = {};
|
216 | Object.keys($ref).forEach(function (key) {
|
217 | if (key !== '$ref') {
|
218 | merged[key] = $ref[key];
|
219 | }
|
220 | });
|
221 | Object.keys(resolvedValue).forEach(function (key) {
|
222 | if (!(key in merged)) {
|
223 | merged[key] = resolvedValue[key];
|
224 | }
|
225 | });
|
226 | return merged;
|
227 | }
|
228 | else {
|
229 | // Completely replace the original reference with the resolved value
|
230 | return resolvedValue;
|
231 | }
|
232 | };
|