@apidevtools/json-schema-ref-parser 10.0.0 → 10.1.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.
Files changed (103) hide show
  1. package/dist/lib/bundle.d.ts +10 -0
  2. package/dist/lib/bundle.js +265 -0
  3. package/dist/lib/dereference.d.ts +9 -0
  4. package/dist/lib/dereference.js +206 -0
  5. package/dist/lib/index.d.ts +206 -0
  6. package/dist/lib/index.js +223 -0
  7. package/dist/lib/normalize-args.d.ts +10 -0
  8. package/dist/lib/normalize-args.js +42 -0
  9. package/dist/lib/options.d.ts +77 -0
  10. package/dist/lib/options.js +119 -0
  11. package/dist/lib/parse.d.ts +8 -0
  12. package/dist/lib/parse.js +177 -0
  13. package/dist/lib/parsers/binary.d.ts +3 -0
  14. package/dist/lib/parsers/binary.js +35 -0
  15. package/dist/lib/parsers/json.d.ts +3 -0
  16. package/dist/lib/parsers/json.js +57 -0
  17. package/dist/lib/parsers/text.d.ts +3 -0
  18. package/dist/lib/parsers/text.js +42 -0
  19. package/dist/lib/parsers/yaml.d.ts +3 -0
  20. package/dist/lib/parsers/yaml.js +65 -0
  21. package/dist/lib/pointer.d.ts +87 -0
  22. package/dist/lib/pointer.js +256 -0
  23. package/dist/lib/ref.d.ts +181 -0
  24. package/dist/lib/ref.js +239 -0
  25. package/dist/lib/refs.d.ts +127 -0
  26. package/dist/lib/refs.js +223 -0
  27. package/dist/lib/resolve-external.d.ts +14 -0
  28. package/dist/lib/resolve-external.js +148 -0
  29. package/dist/lib/resolvers/file.d.ts +3 -0
  30. package/dist/lib/resolvers/file.js +76 -0
  31. package/dist/lib/resolvers/http.d.ts +3 -0
  32. package/dist/lib/resolvers/http.js +159 -0
  33. package/dist/lib/types/index.d.ts +104 -0
  34. package/dist/lib/types/index.js +2 -0
  35. package/dist/lib/util/errors.d.ts +50 -0
  36. package/dist/lib/util/errors.js +106 -0
  37. package/dist/lib/util/maybe.d.ts +3 -0
  38. package/dist/lib/util/maybe.js +24 -0
  39. package/dist/lib/util/next.d.ts +2 -0
  40. package/dist/lib/util/next.js +16 -0
  41. package/dist/lib/util/plugins.d.ts +36 -0
  42. package/dist/lib/util/plugins.js +144 -0
  43. package/dist/lib/util/url.d.ts +93 -0
  44. package/{cjs → dist/lib}/util/url.js +134 -102
  45. package/dist/vite.config.d.ts +2 -0
  46. package/dist/vite.config.js +23 -0
  47. package/lib/{bundle.js → bundle.ts} +105 -101
  48. package/lib/{dereference.js → dereference.ts} +113 -52
  49. package/lib/index.ts +413 -0
  50. package/lib/{normalize-args.js → normalize-args.ts} +7 -14
  51. package/lib/options.ts +202 -0
  52. package/lib/parse.ts +153 -0
  53. package/lib/parsers/binary.ts +39 -0
  54. package/lib/parsers/{json.js → json.ts} +9 -22
  55. package/lib/parsers/text.ts +46 -0
  56. package/lib/parsers/{yaml.js → yaml.ts} +15 -19
  57. package/lib/pointer.ts +296 -0
  58. package/lib/ref.ts +288 -0
  59. package/lib/refs.ts +238 -0
  60. package/lib/{resolve-external.js → resolve-external.ts} +39 -36
  61. package/lib/resolvers/file.ts +40 -0
  62. package/lib/resolvers/http.ts +136 -0
  63. package/lib/tsconfig.json +103 -0
  64. package/lib/types/index.ts +135 -0
  65. package/lib/util/errors.ts +141 -0
  66. package/lib/util/maybe.ts +22 -0
  67. package/lib/util/next.ts +13 -0
  68. package/lib/util/{plugins.js → plugins.ts} +58 -57
  69. package/lib/util/{url.js → url.ts} +64 -83
  70. package/package.json +44 -45
  71. package/cjs/bundle.js +0 -304
  72. package/cjs/dereference.js +0 -258
  73. package/cjs/index.js +0 -603
  74. package/cjs/normalize-args.js +0 -64
  75. package/cjs/options.js +0 -125
  76. package/cjs/package.json +0 -3
  77. package/cjs/parse.js +0 -338
  78. package/cjs/parsers/binary.js +0 -54
  79. package/cjs/parsers/json.js +0 -199
  80. package/cjs/parsers/text.js +0 -61
  81. package/cjs/parsers/yaml.js +0 -239
  82. package/cjs/pointer.js +0 -290
  83. package/cjs/ref.js +0 -333
  84. package/cjs/refs.js +0 -214
  85. package/cjs/resolve-external.js +0 -333
  86. package/cjs/resolvers/file.js +0 -106
  87. package/cjs/resolvers/http.js +0 -184
  88. package/cjs/util/errors.js +0 -401
  89. package/cjs/util/plugins.js +0 -159
  90. package/cjs/util/projectDir.cjs +0 -6
  91. package/lib/index.d.ts +0 -496
  92. package/lib/index.js +0 -290
  93. package/lib/options.js +0 -128
  94. package/lib/parse.js +0 -162
  95. package/lib/parsers/binary.js +0 -53
  96. package/lib/parsers/text.js +0 -64
  97. package/lib/pointer.js +0 -293
  98. package/lib/ref.js +0 -292
  99. package/lib/refs.js +0 -196
  100. package/lib/resolvers/file.js +0 -63
  101. package/lib/resolvers/http.js +0 -155
  102. package/lib/util/errors.js +0 -134
  103. package/lib/util/projectDir.cjs +0 -6
package/lib/parse.ts ADDED
@@ -0,0 +1,153 @@
1
+ import { ono } from "@jsdevtools/ono";
2
+ import * as url from "./util/url.js";
3
+ import * as plugins from "./util/plugins.js";
4
+ import {
5
+ ResolverError,
6
+ ParserError,
7
+ UnmatchedParserError,
8
+ UnmatchedResolverError,
9
+ isHandledError,
10
+ } from "./util/errors.js";
11
+ import type $Refs from "./refs.js";
12
+ import type { Options } from "./options.js";
13
+ import type { FileInfo } from "./types/index.js";
14
+
15
+ export default parse;
16
+
17
+ /**
18
+ * Reads and parses the specified file path or URL.
19
+ */
20
+ async function parse(path: string, $refs: $Refs, options: Options) {
21
+ // Remove the URL fragment, if any
22
+ path = url.stripHash(path);
23
+
24
+ // Add a new $Ref for this file, even though we don't have the value yet.
25
+ // This ensures that we don't simultaneously read & parse the same file multiple times
26
+ const $ref = $refs._add(path);
27
+
28
+ // This "file object" will be passed to all resolvers and parsers.
29
+ const file = {
30
+ url: path,
31
+ extension: url.getExtension(path),
32
+ } as FileInfo;
33
+
34
+ // Read the file and then parse the data
35
+ try {
36
+ const resolver = await readFile(file, options, $refs);
37
+ $ref.pathType = resolver.plugin.name;
38
+ file.data = resolver.result;
39
+
40
+ const parser = await parseFile(file, options, $refs);
41
+ $ref.value = parser.result;
42
+
43
+ return parser.result;
44
+ } catch (err) {
45
+ if (isHandledError(err)) {
46
+ $ref.value = err;
47
+ }
48
+
49
+ throw err;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Reads the given file, using the configured resolver plugins
55
+ *
56
+ * @param file - An object containing information about the referenced file
57
+ * @param file.url - The full URL of the referenced file
58
+ * @param file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
59
+ * @param options
60
+ *
61
+ * @returns
62
+ * The promise resolves with the raw file contents and the resolver that was used.
63
+ */
64
+ async function readFile(file: FileInfo, options: Options, $refs: $Refs): Promise<any> {
65
+ // console.log('Reading %s', file.url);
66
+
67
+ // Find the resolvers that can read this file
68
+ let resolvers = plugins.all(options.resolve);
69
+ resolvers = plugins.filter(resolvers, "canRead", file);
70
+
71
+ // Run the resolvers, in order, until one of them succeeds
72
+ plugins.sort(resolvers);
73
+ try {
74
+ const data = await plugins.run(resolvers, "read", file, $refs);
75
+ return data;
76
+ } catch (err: any) {
77
+ if (!err && options.continueOnError) {
78
+ // No resolver could be matched
79
+ throw new UnmatchedResolverError(file.url);
80
+ } else if (!err || !("error" in err)) {
81
+ // Throw a generic, friendly error.
82
+ throw ono.syntax(`Unable to resolve $ref pointer "${file.url}"`);
83
+ }
84
+ // Throw the original error, if it's one of our own (user-friendly) errors.
85
+ else if (err.error instanceof ResolverError) {
86
+ throw err.error;
87
+ } else {
88
+ throw new ResolverError(err, file.url);
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Parses the given file's contents, using the configured parser plugins.
95
+ *
96
+ * @param file - An object containing information about the referenced file
97
+ * @param file.url - The full URL of the referenced file
98
+ * @param file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
99
+ * @param file.data - The file contents. This will be whatever data type was returned by the resolver
100
+ * @param options
101
+ *
102
+ * @returns
103
+ * The promise resolves with the parsed file contents and the parser that was used.
104
+ */
105
+ async function parseFile(file: FileInfo, options: Options, $refs: $Refs) {
106
+ // console.log('Parsing %s', file.url);
107
+
108
+ // Find the parsers that can read this file type.
109
+ // If none of the parsers are an exact match for this file, then we'll try ALL of them.
110
+ // This handles situations where the file IS a supported type, just with an unknown extension.
111
+ const allParsers = plugins.all(options.parse);
112
+ const filteredParsers = plugins.filter(allParsers, "canParse", file);
113
+ const parsers = filteredParsers.length > 0 ? filteredParsers : allParsers;
114
+
115
+ // Run the parsers, in order, until one of them succeeds
116
+ plugins.sort(parsers);
117
+ try {
118
+ const parser = await plugins.run(parsers, "parse", file, $refs);
119
+ if (!parser.plugin.allowEmpty && isEmpty(parser.result)) {
120
+ throw ono.syntax(`Error parsing "${file.url}" as ${parser.plugin.name}. \nParsed value is empty`);
121
+ } else {
122
+ return parser;
123
+ }
124
+ } catch (err: any) {
125
+ if (!err && options.continueOnError) {
126
+ // No resolver could be matched
127
+ throw new UnmatchedParserError(file.url);
128
+ } else if (err && err.message && err.message.startsWith("Error parsing")) {
129
+ throw err;
130
+ } else if (!err || !("error" in err)) {
131
+ throw ono.syntax(`Unable to parse ${file.url}`);
132
+ } else if (err.error instanceof ParserError) {
133
+ throw err.error;
134
+ } else {
135
+ throw new ParserError(err.error.message, file.url);
136
+ }
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Determines whether the parsed value is "empty".
142
+ *
143
+ * @param value
144
+ * @returns
145
+ */
146
+ function isEmpty(value: any) {
147
+ return (
148
+ value === undefined ||
149
+ (typeof value === "object" && Object.keys(value).length === 0) ||
150
+ (typeof value === "string" && value.trim().length === 0) ||
151
+ (Buffer.isBuffer(value) && value.length === 0)
152
+ );
153
+ }
@@ -0,0 +1,39 @@
1
+ import type { FileInfo } from "../types/index.js";
2
+ import type { Plugin } from "../types/index.js";
3
+
4
+ const BINARY_REGEXP = /\.(jpeg|jpg|gif|png|bmp|ico)$/i;
5
+
6
+ export default {
7
+ /**
8
+ * The order that this parser will run, in relation to other parsers.
9
+ */
10
+ order: 400,
11
+
12
+ /**
13
+ * Whether to allow "empty" files (zero bytes).
14
+ */
15
+ allowEmpty: true,
16
+
17
+ /**
18
+ * Determines whether this parser can parse a given file reference.
19
+ * Parsers that return true will be tried, in order, until one successfully parses the file.
20
+ * Parsers that return false will be skipped, UNLESS all parsers returned false, in which case
21
+ * every parser will be tried.
22
+ */
23
+ canParse(file: FileInfo) {
24
+ // Use this parser if the file is a Buffer, and has a known binary extension
25
+ return Buffer.isBuffer(file.data) && BINARY_REGEXP.test(file.url);
26
+ },
27
+
28
+ /**
29
+ * Parses the given data as a Buffer (byte array).
30
+ */
31
+ parse(file: FileInfo) {
32
+ if (Buffer.isBuffer(file.data)) {
33
+ return file.data;
34
+ } else {
35
+ // This will reject if data is anything other than a string or typed array
36
+ return Buffer.from(file.data);
37
+ }
38
+ },
39
+ } as Plugin;
@@ -1,17 +1,15 @@
1
1
  import { ParserError } from "../util/errors.js";
2
+ import type { FileInfo } from "../types/index.js";
3
+ import type { Plugin } from "../types/index.js";
2
4
 
3
5
  export default {
4
6
  /**
5
7
  * The order that this parser will run, in relation to other parsers.
6
- *
7
- * @type {number}
8
8
  */
9
9
  order: 100,
10
10
 
11
11
  /**
12
12
  * Whether to allow "empty" files. This includes zero-byte files, as well as empty JSON objects.
13
- *
14
- * @type {boolean}
15
13
  */
16
14
  allowEmpty: true,
17
15
 
@@ -20,21 +18,13 @@ export default {
20
18
  * Parsers that match will be tried, in order, until one successfully parses the file.
21
19
  * Parsers that don't match will be skipped, UNLESS none of the parsers match, in which case
22
20
  * every parser will be tried.
23
- *
24
- * @type {RegExp|string|string[]|function}
25
21
  */
26
22
  canParse: ".json",
27
23
 
28
24
  /**
29
25
  * Parses the given file as JSON
30
- *
31
- * @param {object} file - An object containing information about the referenced file
32
- * @param {string} file.url - The full URL of the referenced file
33
- * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
34
- * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver
35
- * @returns {Promise}
36
26
  */
37
- async parse (file) { // eslint-disable-line require-await
27
+ async parse(file: FileInfo): Promise<object | undefined> {
38
28
  let data = file.data;
39
29
  if (Buffer.isBuffer(data)) {
40
30
  data = data.toString();
@@ -43,19 +33,16 @@ export default {
43
33
  if (typeof data === "string") {
44
34
  if (data.trim().length === 0) {
45
35
  return; // This mirrors the YAML behavior
46
- }
47
- else {
36
+ } else {
48
37
  try {
49
38
  return JSON.parse(data);
50
- }
51
- catch (e) {
39
+ } catch (e: any) {
52
40
  throw new ParserError(e.message, file.url);
53
41
  }
54
42
  }
55
- }
56
- else {
43
+ } else {
57
44
  // data is already a JavaScript value (object, array, number, null, NaN, etc.)
58
- return data;
45
+ return data as object;
59
46
  }
60
- }
61
- };
47
+ },
48
+ } as Plugin;
@@ -0,0 +1,46 @@
1
+ import { ParserError } from "../util/errors.js";
2
+ import type { FileInfo } from "../types/index.js";
3
+ import type { Plugin } from "../types/index.js";
4
+
5
+ const TEXT_REGEXP = /\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i;
6
+
7
+ export default {
8
+ /**
9
+ * The order that this parser will run, in relation to other parsers.
10
+ */
11
+ order: 300,
12
+
13
+ /**
14
+ * Whether to allow "empty" files (zero bytes).
15
+ */
16
+ allowEmpty: true,
17
+
18
+ /**
19
+ * The encoding that the text is expected to be in.
20
+ */
21
+ encoding: "utf8" as BufferEncoding,
22
+
23
+ /**
24
+ * Determines whether this parser can parse a given file reference.
25
+ * Parsers that return true will be tried, in order, until one successfully parses the file.
26
+ * Parsers that return false will be skipped, UNLESS all parsers returned false, in which case
27
+ * every parser will be tried.
28
+ */
29
+ canParse(file: FileInfo) {
30
+ // Use this parser if the file is a string or Buffer, and has a known text-based extension
31
+ return (typeof file.data === "string" || Buffer.isBuffer(file.data)) && TEXT_REGEXP.test(file.url);
32
+ },
33
+
34
+ /**
35
+ * Parses the given file as text
36
+ */
37
+ parse(file: FileInfo) {
38
+ if (typeof file.data === "string") {
39
+ return file.data;
40
+ } else if (Buffer.isBuffer(file.data)) {
41
+ return file.data.toString(this.encoding);
42
+ } else {
43
+ throw new ParserError("data is not text", file.url);
44
+ }
45
+ },
46
+ } as Plugin;
@@ -1,19 +1,17 @@
1
1
  import { ParserError } from "../util/errors.js";
2
2
  import yaml from "js-yaml";
3
3
  import { JSON_SCHEMA } from "js-yaml";
4
+ import type { FileInfo } from "../types/index.js";
5
+ import type { Plugin } from "../types/index.js";
4
6
 
5
7
  export default {
6
8
  /**
7
9
  * The order that this parser will run, in relation to other parsers.
8
- *
9
- * @type {number}
10
10
  */
11
11
  order: 200,
12
12
 
13
13
  /**
14
14
  * Whether to allow "empty" files. This includes zero-byte files, as well as empty JSON objects.
15
- *
16
- * @type {boolean}
17
15
  */
18
16
  allowEmpty: true,
19
17
 
@@ -22,21 +20,20 @@ export default {
22
20
  * Parsers that match will be tried, in order, until one successfully parses the file.
23
21
  * Parsers that don't match will be skipped, UNLESS none of the parsers match, in which case
24
22
  * every parser will be tried.
25
- *
26
- * @type {RegExp|string[]|function}
27
23
  */
28
- canParse: [".yaml", ".yml", ".json"], // JSON is valid YAML
24
+ canParse: [".yaml", ".yml", ".json"], // JSON is valid YAML
29
25
 
30
26
  /**
31
27
  * Parses the given file as YAML
32
28
  *
33
- * @param {object} file - An object containing information about the referenced file
34
- * @param {string} file.url - The full URL of the referenced file
35
- * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
36
- * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver
37
- * @returns {Promise}
29
+ * @param file - An object containing information about the referenced file
30
+ * @param file.url - The full URL of the referenced file
31
+ * @param file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.)
32
+ * @param file.data - The file contents. This will be whatever data type was returned by the resolver
33
+ * @returns
38
34
  */
39
- async parse (file) { // eslint-disable-line require-await
35
+ async parse(file: FileInfo) {
36
+ // eslint-disable-line require-await
40
37
  let data = file.data;
41
38
  if (Buffer.isBuffer(data)) {
42
39
  data = data.toString();
@@ -45,14 +42,13 @@ export default {
45
42
  if (typeof data === "string") {
46
43
  try {
47
44
  return yaml.load(data, { schema: JSON_SCHEMA });
48
- }
49
- catch (e) {
45
+ } catch (e) {
46
+ // @ts-expect-error TS(2571): Object is of type 'unknown'.
50
47
  throw new ParserError(e.message, file.url);
51
48
  }
52
- }
53
- else {
49
+ } else {
54
50
  // data is already a JavaScript value (object, array, number, null, NaN, etc.)
55
51
  return data;
56
52
  }
57
- }
58
- };
53
+ },
54
+ } as Plugin;
package/lib/pointer.ts ADDED
@@ -0,0 +1,296 @@
1
+ import type $RefParserOptions from "./options.js";
2
+
3
+ import $Ref from "./ref.js";
4
+ import * as url from "./util/url.js";
5
+ import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
6
+ const slashes = /\//g;
7
+ const tildes = /~/g;
8
+ const escapedSlash = /~1/g;
9
+ const escapedTilde = /~0/g;
10
+
11
+ /**
12
+ * This class represents a single JSON pointer and its resolved value.
13
+ *
14
+ * @param $ref
15
+ * @param path
16
+ * @param [friendlyPath] - The original user-specified path (used for error messages)
17
+ * @class
18
+ */
19
+ class Pointer {
20
+ /**
21
+ * The {@link $Ref} object that contains this {@link Pointer} object.
22
+ */
23
+ $ref: $Ref;
24
+
25
+ /**
26
+ * The file path or URL, containing the JSON pointer in the hash.
27
+ * This path is relative to the path of the main JSON schema file.
28
+ */
29
+ path: string;
30
+
31
+ /**
32
+ * The original path or URL, used for error messages.
33
+ */
34
+ originalPath: string;
35
+
36
+ /**
37
+ * The value of the JSON pointer.
38
+ * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
39
+ */
40
+
41
+ value: any;
42
+ /**
43
+ * Indicates whether the pointer references itself.
44
+ */
45
+ circular: boolean;
46
+ /**
47
+ * The number of indirect references that were traversed to resolve the value.
48
+ * Resolving a single pointer may require resolving multiple $Refs.
49
+ */
50
+ indirections: number;
51
+
52
+ constructor($ref: any, path: any, friendlyPath: any) {
53
+ this.$ref = $ref;
54
+
55
+ this.path = path;
56
+
57
+ this.originalPath = friendlyPath || path;
58
+
59
+ this.value = undefined;
60
+
61
+ this.circular = false;
62
+
63
+ this.indirections = 0;
64
+ }
65
+
66
+ /**
67
+ * Resolves the value of a nested property within the given object.
68
+ *
69
+ * @param obj - The object that will be crawled
70
+ * @param options
71
+ * @param pathFromRoot - the path of place that initiated resolving
72
+ *
73
+ * @returns
74
+ * Returns a JSON pointer whose {@link Pointer#value} is the resolved value.
75
+ * If resolving this value required resolving other JSON references, then
76
+ * the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path
77
+ * of the resolved value.
78
+ */
79
+ resolve(obj: any, options: any, pathFromRoot: any) {
80
+ const tokens = Pointer.parse(this.path, this.originalPath);
81
+
82
+ // Crawl the object, one token at a time
83
+ this.value = unwrapOrThrow(obj);
84
+
85
+ for (let i = 0; i < tokens.length; i++) {
86
+ if (resolveIf$Ref(this, options)) {
87
+ // The $ref path has changed, so append the remaining tokens to the path
88
+ this.path = Pointer.join(this.path, tokens.slice(i));
89
+ }
90
+
91
+ if (typeof this.value === "object" && this.value !== null && "$ref" in this.value) {
92
+ return this;
93
+ }
94
+
95
+ const token = tokens[i];
96
+ if (this.value[token] === undefined || this.value[token] === null) {
97
+ this.value = null;
98
+ throw new MissingPointerError(token, decodeURI(this.originalPath));
99
+ } else {
100
+ this.value = this.value[token];
101
+ }
102
+ }
103
+
104
+ // Resolve the final value
105
+ if (!this.value || (this.value.$ref && url.resolve(this.path, this.value.$ref) !== pathFromRoot)) {
106
+ resolveIf$Ref(this, options);
107
+ }
108
+
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Sets the value of a nested property within the given object.
114
+ *
115
+ * @param obj - The object that will be crawled
116
+ * @param value - the value to assign
117
+ * @param options
118
+ *
119
+ * @returns
120
+ * Returns the modified object, or an entirely new object if the entire object is overwritten.
121
+ */
122
+ set(obj: any, value: any, options?: $RefParserOptions) {
123
+ const tokens = Pointer.parse(this.path);
124
+ let token;
125
+
126
+ if (tokens.length === 0) {
127
+ // There are no tokens, replace the entire object with the new value
128
+ this.value = value;
129
+ return value;
130
+ }
131
+
132
+ // Crawl the object, one token at a time
133
+ this.value = unwrapOrThrow(obj);
134
+
135
+ for (let i = 0; i < tokens.length - 1; i++) {
136
+ resolveIf$Ref(this, options);
137
+
138
+ token = tokens[i];
139
+ if (this.value && this.value[token] !== undefined) {
140
+ // The token exists
141
+ this.value = this.value[token];
142
+ } else {
143
+ // The token doesn't exist, so create it
144
+ this.value = setValue(this, token, {});
145
+ }
146
+ }
147
+
148
+ // Set the value of the final token
149
+ resolveIf$Ref(this, options);
150
+ token = tokens[tokens.length - 1];
151
+ setValue(this, token, value);
152
+
153
+ // Return the updated object
154
+ return obj;
155
+ }
156
+
157
+ /**
158
+ * Parses a JSON pointer (or a path containing a JSON pointer in the hash)
159
+ * and returns an array of the pointer's tokens.
160
+ * (e.g. "schema.json#/definitions/person/name" => ["definitions", "person", "name"])
161
+ *
162
+ * The pointer is parsed according to RFC 6901
163
+ * {@link https://tools.ietf.org/html/rfc6901#section-3}
164
+ *
165
+ * @param path
166
+ * @param [originalPath]
167
+ * @returns
168
+ */
169
+ static parse(path: string, originalPath?: string) {
170
+ // Get the JSON pointer from the path's hash
171
+ let pointer = url.getHash(path).substr(1);
172
+
173
+ // If there's no pointer, then there are no tokens,
174
+ // so return an empty array
175
+ if (!pointer) {
176
+ return [];
177
+ }
178
+
179
+ // Split into an array
180
+ pointer = pointer.split("/");
181
+
182
+ // Decode each part, according to RFC 6901
183
+ for (let i = 0; i < pointer.length; i++) {
184
+ pointer[i] = decodeURIComponent(pointer[i].replace(escapedSlash, "/").replace(escapedTilde, "~"));
185
+ }
186
+
187
+ if (pointer[0] !== "") {
188
+ throw new InvalidPointerError(pointer, originalPath === undefined ? path : originalPath);
189
+ }
190
+
191
+ return pointer.slice(1);
192
+ }
193
+
194
+ /**
195
+ * Creates a JSON pointer path, by joining one or more tokens to a base path.
196
+ *
197
+ * @param base - The base path (e.g. "schema.json#/definitions/person")
198
+ * @param tokens - The token(s) to append (e.g. ["name", "first"])
199
+ * @returns
200
+ */
201
+ static join(base: any, tokens: any) {
202
+ // Ensure that the base path contains a hash
203
+ if (base.indexOf("#") === -1) {
204
+ base += "#";
205
+ }
206
+
207
+ // Append each token to the base path
208
+ tokens = Array.isArray(tokens) ? tokens : [tokens];
209
+ for (let i = 0; i < tokens.length; i++) {
210
+ const token = tokens[i];
211
+ // Encode the token, according to RFC 6901
212
+ base += "/" + encodeURIComponent(token.replace(tildes, "~0").replace(slashes, "~1"));
213
+ }
214
+
215
+ return base;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * If the given pointer's {@link Pointer#value} is a JSON reference,
221
+ * then the reference is resolved and {@link Pointer#value} is replaced with the resolved value.
222
+ * In addition, {@link Pointer#path} and {@link Pointer#$ref} are updated to reflect the
223
+ * resolution path of the new value.
224
+ *
225
+ * @param pointer
226
+ * @param options
227
+ * @returns - Returns `true` if the resolution path changed
228
+ */
229
+ function resolveIf$Ref(pointer: any, options: any) {
230
+ // Is the value a JSON reference? (and allowed?)
231
+
232
+ if ($Ref.isAllowed$Ref(pointer.value, options)) {
233
+ const $refPath = url.resolve(pointer.path, pointer.value.$ref);
234
+
235
+ if ($refPath === pointer.path) {
236
+ // The value is a reference to itself, so there's nothing to do.
237
+ pointer.circular = true;
238
+ } else {
239
+ const resolved = pointer.$ref.$refs._resolve($refPath, pointer.path, options);
240
+ if (resolved === null) {
241
+ return false;
242
+ }
243
+
244
+ pointer.indirections += resolved.indirections + 1;
245
+
246
+ if ($Ref.isExtended$Ref(pointer.value)) {
247
+ // This JSON reference "extends" the resolved value, rather than simply pointing to it.
248
+ // So the resolved path does NOT change. Just the value does.
249
+ pointer.value = $Ref.dereference(pointer.value, resolved.value);
250
+ return false;
251
+ } else {
252
+ // Resolve the reference
253
+ pointer.$ref = resolved.$ref;
254
+ pointer.path = resolved.path;
255
+ pointer.value = resolved.value;
256
+ }
257
+
258
+ return true;
259
+ }
260
+ }
261
+ }
262
+ export default Pointer;
263
+
264
+ /**
265
+ * Sets the specified token value of the {@link Pointer#value}.
266
+ *
267
+ * The token is evaluated according to RFC 6901.
268
+ * {@link https://tools.ietf.org/html/rfc6901#section-4}
269
+ *
270
+ * @param pointer - The JSON Pointer whose value will be modified
271
+ * @param token - A JSON Pointer token that indicates how to modify `obj`
272
+ * @param value - The value to assign
273
+ * @returns - Returns the assigned value
274
+ */
275
+ function setValue(pointer: any, token: any, value: any) {
276
+ if (pointer.value && typeof pointer.value === "object") {
277
+ if (token === "-" && Array.isArray(pointer.value)) {
278
+ pointer.value.push(value);
279
+ } else {
280
+ pointer.value[token] = value;
281
+ }
282
+ } else {
283
+ throw new JSONParserError(
284
+ `Error assigning $ref pointer "${pointer.path}". \nCannot set "${token}" of a non-object.`,
285
+ );
286
+ }
287
+ return value;
288
+ }
289
+
290
+ function unwrapOrThrow(value: any) {
291
+ if (isHandledError(value)) {
292
+ throw value;
293
+ }
294
+
295
+ return value;
296
+ }