@apidevtools/json-schema-ref-parser 11.8.2 → 11.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -157,8 +157,8 @@ JSON Schema $Ref Parser is 100% free and open-source, under the [MIT license](LI
157
157
 
158
158
  ## Thanks
159
159
 
160
- Thanks to these awesome companies for their support of Open Source developers ❤
160
+ Thanks to these awesome contributors for their major support of this open-source project.
161
161
 
162
- - [Stoplight](https://stoplight.io/?utm_source=github&utm_medium=readme&utm_campaign=json_schema_ref_parser)
163
- - [Phil Sturgeon](https://philsturgeon.com)
164
162
  - [JonLuca De Caro](https://jonlu.ca)
163
+ - [Phil Sturgeon](https://philsturgeon.com)
164
+ - [Stoplight](https://stoplight.io/?utm_source=github&utm_medium=readme&utm_campaign=json_schema_ref_parser)
@@ -106,7 +106,28 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
106
106
  circular = dereferenced.circular;
107
107
  // Avoid pointless mutations; breaks frozen objects to no profit
108
108
  if (obj[key] !== dereferenced.value) {
109
+ // If we have properties we want to preserve from our dereferenced schema then we need
110
+ // to copy them over to our new object.
111
+ const preserved = new Map();
112
+ if (derefOptions?.preservedProperties) {
113
+ if (typeof obj[key] === "object" && !Array.isArray(obj[key])) {
114
+ derefOptions?.preservedProperties.forEach((prop) => {
115
+ if (prop in obj[key]) {
116
+ preserved.set(prop, obj[key][prop]);
117
+ }
118
+ });
119
+ }
120
+ }
109
121
  obj[key] = dereferenced.value;
122
+ // If we have data to preserve and our dereferenced object is still an object then
123
+ // we need copy back our preserved data into our dereferenced schema.
124
+ if (derefOptions?.preservedProperties) {
125
+ if (preserved.size && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
126
+ preserved.forEach((value, prop) => {
127
+ obj[key][prop] = value;
128
+ });
129
+ }
130
+ }
110
131
  derefOptions?.onDereference?.(value.$ref, obj[key], obj, key);
111
132
  }
112
133
  }
@@ -32,6 +32,15 @@ export interface DereferenceOptions {
32
32
  * @argument {string} parentPropName - The prop name of the parent object whose value was dereferenced
33
33
  */
34
34
  onDereference?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void;
35
+ /**
36
+ * An array of properties to preserve when dereferencing a `$ref` schema. Useful if you want to
37
+ * enforce non-standard dereferencing behavior like present in the OpenAPI 3.1 specification where
38
+ * `description` and `summary` properties are preserved when alongside a `$ref` pointer.
39
+ *
40
+ * If none supplied then no properties will be preserved and the object will be fully replaced
41
+ * with the dereferenced `$ref`.
42
+ */
43
+ preservedProperties?: string[];
35
44
  /**
36
45
  * Whether a reference should resolve relative to its directory/path, or from the cwd
37
46
  *
@@ -384,6 +393,7 @@ export declare const getNewOptions: <S extends object = JSONSchema, O extends Pa
384
393
  excludedPathMatcher?: {} | undefined;
385
394
  onCircular?: {} | undefined;
386
395
  onDereference?: {} | undefined;
396
+ preservedProperties?: (string | undefined)[] | undefined;
387
397
  externalReferenceResolution?: "relative" | "root" | undefined;
388
398
  } | undefined;
389
399
  mutateInputSchema?: boolean | undefined;
@@ -123,7 +123,31 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
123
123
  circular = dereferenced.circular;
124
124
  // Avoid pointless mutations; breaks frozen objects to no profit
125
125
  if (obj[key] !== dereferenced.value) {
126
+ // If we have properties we want to preserve from our dereferenced schema then we need
127
+ // to copy them over to our new object.
128
+ const preserved: Map<string, unknown> = new Map();
129
+ if (derefOptions?.preservedProperties) {
130
+ if (typeof obj[key] === "object" && !Array.isArray(obj[key])) {
131
+ derefOptions?.preservedProperties.forEach((prop) => {
132
+ if (prop in obj[key]) {
133
+ preserved.set(prop, obj[key][prop]);
134
+ }
135
+ });
136
+ }
137
+ }
138
+
126
139
  obj[key] = dereferenced.value;
140
+
141
+ // If we have data to preserve and our dereferenced object is still an object then
142
+ // we need copy back our preserved data into our dereferenced schema.
143
+ if (derefOptions?.preservedProperties) {
144
+ if (preserved.size && typeof obj[key] === "object" && !Array.isArray(obj[key])) {
145
+ preserved.forEach((value, prop) => {
146
+ obj[key][prop] = value;
147
+ });
148
+ }
149
+ }
150
+
127
151
  derefOptions?.onDereference?.(value.$ref, obj[key], obj, key);
128
152
  }
129
153
  } else {
package/lib/options.ts CHANGED
@@ -46,6 +46,16 @@ export interface DereferenceOptions {
46
46
  */
47
47
  onDereference?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void;
48
48
 
49
+ /**
50
+ * An array of properties to preserve when dereferencing a `$ref` schema. Useful if you want to
51
+ * enforce non-standard dereferencing behavior like present in the OpenAPI 3.1 specification where
52
+ * `description` and `summary` properties are preserved when alongside a `$ref` pointer.
53
+ *
54
+ * If none supplied then no properties will be preserved and the object will be fully replaced
55
+ * with the dereferenced `$ref`.
56
+ */
57
+ preservedProperties?: string[];
58
+
49
59
  /**
50
60
  * Whether a reference should resolve relative to its directory/path, or from the cwd
51
61
  *
@@ -126,7 +126,7 @@ export class MissingPointerError extends JSONParserError {
126
126
  public targetToken: any;
127
127
  public targetRef: string;
128
128
  public targetFound: string;
129
- public parentPath: string;
129
+ public parentPath: string;
130
130
  constructor(token: any, path: any, targetRef: any, targetFound: any, parentPath: any) {
131
131
  super(`Missing $ref pointer "${getHash(path)}". Token "${token}" does not exist.`, stripHash(path));
132
132
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apidevtools/json-schema-ref-parser",
3
- "version": "11.8.2",
3
+ "version": "11.9.0",
4
4
  "description": "Parse, Resolve, and Dereference JSON Schema $ref pointers",
5
5
  "scripts": {
6
6
  "prepublishOnly": "yarn build",
package/cjs/bundle.js DELETED
@@ -1,304 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "default", {
6
- enumerable: true,
7
- get: function() {
8
- return _default;
9
- }
10
- });
11
- var _refJs = /*#__PURE__*/ _interopRequireDefault(require("./ref.js"));
12
- var _pointerJs = /*#__PURE__*/ _interopRequireDefault(require("./pointer.js"));
13
- var _urlJs = /*#__PURE__*/ _interopRequireWildcard(require("./util/url.js"));
14
- function _interopRequireDefault(obj) {
15
- return obj && obj.__esModule ? obj : {
16
- default: obj
17
- };
18
- }
19
- function _getRequireWildcardCache(nodeInterop) {
20
- if (typeof WeakMap !== "function") return null;
21
- var cacheBabelInterop = new WeakMap();
22
- var cacheNodeInterop = new WeakMap();
23
- return (_getRequireWildcardCache = function(nodeInterop) {
24
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
25
- })(nodeInterop);
26
- }
27
- function _interopRequireWildcard(obj, nodeInterop) {
28
- if (!nodeInterop && obj && obj.__esModule) {
29
- return obj;
30
- }
31
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
32
- return {
33
- default: obj
34
- };
35
- }
36
- var cache = _getRequireWildcardCache(nodeInterop);
37
- if (cache && cache.has(obj)) {
38
- return cache.get(obj);
39
- }
40
- var newObj = {};
41
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
42
- for(var key in obj){
43
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
44
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
45
- if (desc && (desc.get || desc.set)) {
46
- Object.defineProperty(newObj, key, desc);
47
- } else {
48
- newObj[key] = obj[key];
49
- }
50
- }
51
- }
52
- newObj.default = obj;
53
- if (cache) {
54
- cache.set(obj, newObj);
55
- }
56
- return newObj;
57
- }
58
- var _default = bundle;
59
- /**
60
- * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
61
- * only has *internal* references, not any *external* references.
62
- * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
63
- *
64
- * @param {$RefParser} parser
65
- * @param {$RefParserOptions} options
66
- */ function bundle(parser, options) {
67
- // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
68
- // Build an inventory of all $ref pointers in the JSON Schema
69
- var inventory = [];
70
- crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
71
- // Remap all $ref pointers
72
- remap(inventory);
73
- }
74
- /**
75
- * Recursively crawls the given value, and inventories all JSON references.
76
- *
77
- * @param {object} parent - The object containing the value to crawl. If the value is not an object or array, it will be ignored.
78
- * @param {string} key - The property key of `parent` to be crawled
79
- * @param {string} path - The full path of the property being crawled, possibly with a JSON Pointer in the hash
80
- * @param {string} pathFromRoot - The path of the property being crawled, from the schema root
81
- * @param {object[]} inventory - An array of already-inventoried $ref pointers
82
- * @param {$Refs} $refs
83
- * @param {$RefParserOptions} options
84
- */ function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs, options) {
85
- var obj = key === null ? parent : parent[key];
86
- if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
87
- if (_refJs.default.isAllowed$Ref(obj)) {
88
- inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options);
89
- } else {
90
- // Crawl the object in a specific order that's optimized for bundling.
91
- // This is important because it determines how `pathFromRoot` gets built,
92
- // which later determines which keys get dereferenced and which ones get remapped
93
- var keys = Object.keys(obj).sort(function(a, b) {
94
- // Most people will expect references to be bundled into the the "definitions" property,
95
- // so we always crawl that property first, if it exists.
96
- if (a === "definitions") {
97
- return -1;
98
- } else if (b === "definitions") {
99
- return 1;
100
- } else {
101
- // Otherwise, crawl the keys based on their length.
102
- // This produces the shortest possible bundled references
103
- return a.length - b.length;
104
- }
105
- });
106
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
107
- try {
108
- // eslint-disable-next-line no-shadow
109
- for(var _iterator = keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
110
- var _$key = _step.value;
111
- var keyPath = _pointerJs.default.join(path, _$key);
112
- var keyPathFromRoot = _pointerJs.default.join(pathFromRoot, _$key);
113
- var value = obj[_$key];
114
- if (_refJs.default.isAllowed$Ref(value)) {
115
- inventory$Ref(obj, _$key, path, keyPathFromRoot, indirections, inventory, $refs, options);
116
- } else {
117
- crawl(obj, _$key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
118
- }
119
- }
120
- } catch (err) {
121
- _didIteratorError = true;
122
- _iteratorError = err;
123
- } finally{
124
- try {
125
- if (!_iteratorNormalCompletion && _iterator.return != null) {
126
- _iterator.return();
127
- }
128
- } finally{
129
- if (_didIteratorError) {
130
- throw _iteratorError;
131
- }
132
- }
133
- }
134
- }
135
- }
136
- }
137
- /**
138
- * Inventories the given JSON Reference (i.e. records detailed information about it so we can
139
- * optimize all $refs in the schema), and then crawls the resolved value.
140
- *
141
- * @param {object} $refParent - The object that contains a JSON Reference as one of its keys
142
- * @param {string} $refKey - The key in `$refParent` that is a JSON Reference
143
- * @param {string} path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
144
- * @param {string} pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root
145
- * @param {object[]} inventory - An array of already-inventoried $ref pointers
146
- * @param {$Refs} $refs
147
- * @param {$RefParserOptions} options
148
- */ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, inventory, $refs, options) {
149
- var $ref = $refKey === null ? $refParent : $refParent[$refKey];
150
- var $refPath = _urlJs.resolve(path, $ref.$ref);
151
- var pointer = $refs._resolve($refPath, pathFromRoot, options);
152
- if (pointer === null) {
153
- return;
154
- }
155
- var depth = _pointerJs.default.parse(pathFromRoot).length;
156
- var file = _urlJs.stripHash(pointer.path);
157
- var hash = _urlJs.getHash(pointer.path);
158
- var external = file !== $refs._root$Ref.path;
159
- var extended = _refJs.default.isExtended$Ref($ref);
160
- indirections += pointer.indirections;
161
- var existingEntry = findInInventory(inventory, $refParent, $refKey);
162
- if (existingEntry) {
163
- // This $Ref has already been inventoried, so we don't need to process it again
164
- if (depth < existingEntry.depth || indirections < existingEntry.indirections) {
165
- removeFromInventory(inventory, existingEntry);
166
- } else {
167
- return;
168
- }
169
- }
170
- inventory.push({
171
- $ref: $ref,
172
- parent: $refParent,
173
- key: $refKey,
174
- pathFromRoot: pathFromRoot,
175
- depth: depth,
176
- file: file,
177
- hash: hash,
178
- value: pointer.value,
179
- circular: pointer.circular,
180
- extended: extended,
181
- external: external,
182
- indirections: indirections
183
- });
184
- // Recursively crawl the resolved value
185
- if (!existingEntry || external) {
186
- crawl(pointer.value, null, pointer.path, pathFromRoot, indirections + 1, inventory, $refs, options);
187
- }
188
- }
189
- /**
190
- * Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema.
191
- * Each referenced value is dereferenced EXACTLY ONCE. All subsequent references to the same
192
- * value are re-mapped to point to the first reference.
193
- *
194
- * @example:
195
- * {
196
- * first: { $ref: somefile.json#/some/part },
197
- * second: { $ref: somefile.json#/another/part },
198
- * third: { $ref: somefile.json },
199
- * fourth: { $ref: somefile.json#/some/part/sub/part }
200
- * }
201
- *
202
- * In this example, there are four references to the same file, but since the third reference points
203
- * to the ENTIRE file, that's the only one we need to dereference. The other three can just be
204
- * remapped to point inside the third one.
205
- *
206
- * On the other hand, if the third reference DIDN'T exist, then the first and second would both need
207
- * to be dereferenced, since they point to different parts of the file. The fourth reference does NOT
208
- * need to be dereferenced, because it can be remapped to point inside the first one.
209
- *
210
- * @param {object[]} inventory
211
- */ function remap(inventory) {
212
- // Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
213
- inventory.sort(function(a, b) {
214
- if (a.file !== b.file) {
215
- // Group all the $refs that point to the same file
216
- return a.file < b.file ? -1 : +1;
217
- } else if (a.hash !== b.hash) {
218
- // Group all the $refs that point to the same part of the file
219
- return a.hash < b.hash ? -1 : +1;
220
- } else if (a.circular !== b.circular) {
221
- // If the $ref points to itself, then sort it higher than other $refs that point to this $ref
222
- return a.circular ? -1 : +1;
223
- } else if (a.extended !== b.extended) {
224
- // If the $ref extends the resolved value, then sort it lower than other $refs that don't extend the value
225
- return a.extended ? +1 : -1;
226
- } else if (a.indirections !== b.indirections) {
227
- // Sort direct references higher than indirect references
228
- return a.indirections - b.indirections;
229
- } else if (a.depth !== b.depth) {
230
- // Sort $refs by how close they are to the JSON Schema root
231
- return a.depth - b.depth;
232
- } else {
233
- // Determine how far each $ref is from the "definitions" property.
234
- // Most people will expect references to be bundled into the the "definitions" property if possible.
235
- var aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
236
- var bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
237
- if (aDefinitionsIndex !== bDefinitionsIndex) {
238
- // Give higher priority to the $ref that's closer to the "definitions" property
239
- return bDefinitionsIndex - aDefinitionsIndex;
240
- } else {
241
- // All else is equal, so use the shorter path, which will produce the shortest possible reference
242
- return a.pathFromRoot.length - b.pathFromRoot.length;
243
- }
244
- }
245
- });
246
- var file, hash, pathFromRoot;
247
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
248
- try {
249
- for(var _iterator = inventory[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
250
- var entry = _step.value;
251
- // console.log('Re-mapping $ref pointer "%s" at %s', entry.$ref.$ref, entry.pathFromRoot);
252
- if (!entry.external) {
253
- // This $ref already resolves to the main JSON Schema file
254
- entry.$ref.$ref = entry.hash;
255
- } else if (entry.file === file && entry.hash === hash) {
256
- // This $ref points to the same value as the prevous $ref, so remap it to the same path
257
- entry.$ref.$ref = pathFromRoot;
258
- } else if (entry.file === file && entry.hash.indexOf(hash + "/") === 0) {
259
- // This $ref points to a sub-value of the prevous $ref, so remap it beneath that path
260
- entry.$ref.$ref = _pointerJs.default.join(pathFromRoot, _pointerJs.default.parse(entry.hash.replace(hash, "#")));
261
- } else {
262
- // We've moved to a new file or new hash
263
- file = entry.file;
264
- hash = entry.hash;
265
- pathFromRoot = entry.pathFromRoot;
266
- // This is the first $ref to point to this value, so dereference the value.
267
- // Any other $refs that point to the same value will point to this $ref instead
268
- entry.$ref = entry.parent[entry.key] = _refJs.default.dereference(entry.$ref, entry.value);
269
- if (entry.circular) {
270
- // This $ref points to itself
271
- entry.$ref.$ref = entry.pathFromRoot;
272
- }
273
- }
274
- // console.log(' new value: %s', (entry.$ref && entry.$ref.$ref) ? entry.$ref.$ref : '[object Object]');
275
- }
276
- } catch (err) {
277
- _didIteratorError = true;
278
- _iteratorError = err;
279
- } finally{
280
- try {
281
- if (!_iteratorNormalCompletion && _iterator.return != null) {
282
- _iterator.return();
283
- }
284
- } finally{
285
- if (_didIteratorError) {
286
- throw _iteratorError;
287
- }
288
- }
289
- }
290
- }
291
- /**
292
- * TODO
293
- */ function findInInventory(inventory, $refParent, $refKey) {
294
- for(var i = 0; i < inventory.length; i++){
295
- var existingEntry = inventory[i];
296
- if (existingEntry.parent === $refParent && existingEntry.key === $refKey) {
297
- return existingEntry;
298
- }
299
- }
300
- }
301
- function removeFromInventory(inventory, entry) {
302
- var index = inventory.indexOf(entry);
303
- inventory.splice(index, 1);
304
- }