index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /* eslint-disable no-unused-vars */
  2. "use strict";
  3. const $Refs = require("./refs");
  4. const _parse = require("./parse");
  5. const normalizeArgs = require("./normalize-args");
  6. const resolveExternal = require("./resolve-external");
  7. const _bundle = require("./bundle");
  8. const _dereference = require("./dereference");
  9. const url = require("./util/url");
  10. const { JSONParserError, InvalidPointerError, MissingPointerError, ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError, JSONParserErrorGroup } = require("./util/errors");
  11. const maybe = require("call-me-maybe");
  12. const { ono } = require("@jsdevtools/ono");
  13. module.exports = $RefParser;
  14. module.exports.default = $RefParser;
  15. module.exports.JSONParserError = JSONParserError;
  16. module.exports.InvalidPointerError = InvalidPointerError;
  17. module.exports.MissingPointerError = MissingPointerError;
  18. module.exports.ResolverError = ResolverError;
  19. module.exports.ParserError = ParserError;
  20. module.exports.UnmatchedParserError = UnmatchedParserError;
  21. module.exports.UnmatchedResolverError = UnmatchedResolverError;
  22. /**
  23. * This class parses a JSON schema, builds a map of its JSON references and their resolved values,
  24. * and provides methods for traversing, manipulating, and dereferencing those references.
  25. *
  26. * @constructor
  27. */
  28. function $RefParser () {
  29. /**
  30. * The parsed (and possibly dereferenced) JSON schema object
  31. *
  32. * @type {object}
  33. * @readonly
  34. */
  35. this.schema = null;
  36. /**
  37. * The resolved JSON references
  38. *
  39. * @type {$Refs}
  40. * @readonly
  41. */
  42. this.$refs = new $Refs();
  43. }
  44. /**
  45. * Parses the given JSON schema.
  46. * This method does not resolve any JSON references.
  47. * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
  48. *
  49. * @param {string} [path] - The file path or URL of the JSON schema
  50. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  51. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed
  52. * @param {function} [callback] - An error-first callback. The second parameter is the parsed JSON schema object.
  53. * @returns {Promise} - The returned promise resolves with the parsed JSON schema object.
  54. */
  55. $RefParser.parse = function parse (path, schema, options, callback) {
  56. let Class = this; // eslint-disable-line consistent-this
  57. let instance = new Class();
  58. return instance.parse.apply(instance, arguments);
  59. };
  60. /**
  61. * Parses the given JSON schema.
  62. * This method does not resolve any JSON references.
  63. * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
  64. *
  65. * @param {string} [path] - The file path or URL of the JSON schema
  66. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  67. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed
  68. * @param {function} [callback] - An error-first callback. The second parameter is the parsed JSON schema object.
  69. * @returns {Promise} - The returned promise resolves with the parsed JSON schema object.
  70. */
  71. $RefParser.prototype.parse = async function parse (path, schema, options, callback) {
  72. let args = normalizeArgs(arguments);
  73. let promise;
  74. if (!args.path && !args.schema) {
  75. let err = ono(`Expected a file path, URL, or object. Got ${args.path || args.schema}`);
  76. return maybe(args.callback, Promise.reject(err));
  77. }
  78. // Reset everything
  79. this.schema = null;
  80. this.$refs = new $Refs();
  81. // If the path is a filesystem path, then convert it to a URL.
  82. // NOTE: According to the JSON Reference spec, these should already be URLs,
  83. // but, in practice, many people use local filesystem paths instead.
  84. // So we're being generous here and doing the conversion automatically.
  85. // This is not intended to be a 100% bulletproof solution.
  86. // If it doesn't work for your use-case, then use a URL instead.
  87. let pathType = "http";
  88. if (url.isFileSystemPath(args.path)) {
  89. args.path = url.fromFileSystemPath(args.path);
  90. pathType = "file";
  91. }
  92. // Resolve the absolute path of the schema
  93. args.path = url.resolve(url.cwd(), args.path);
  94. if (args.schema && typeof args.schema === "object") {
  95. // A schema object was passed-in.
  96. // So immediately add a new $Ref with the schema object as its value
  97. let $ref = this.$refs._add(args.path);
  98. $ref.value = args.schema;
  99. $ref.pathType = pathType;
  100. promise = Promise.resolve(args.schema);
  101. }
  102. else {
  103. // Parse the schema file/url
  104. promise = _parse(args.path, this.$refs, args.options);
  105. }
  106. let me = this;
  107. try {
  108. let result = await promise;
  109. if (result !== null && typeof result === "object" && !Buffer.isBuffer(result)) {
  110. me.schema = result;
  111. return maybe(args.callback, Promise.resolve(me.schema));
  112. }
  113. else if (args.options.continueOnError) {
  114. me.schema = null; // it's already set to null at line 79, but let's set it again for the sake of readability
  115. return maybe(args.callback, Promise.resolve(me.schema));
  116. }
  117. else {
  118. throw ono.syntax(`"${me.$refs._root$Ref.path || result}" is not a valid JSON Schema`);
  119. }
  120. }
  121. catch (err) {
  122. if (!args.options.continueOnError || !isHandledError(err)) {
  123. return maybe(args.callback, Promise.reject(err));
  124. }
  125. if (this.$refs._$refs[url.stripHash(args.path)]) {
  126. this.$refs._$refs[url.stripHash(args.path)].addError(err);
  127. }
  128. return maybe(args.callback, Promise.resolve(null));
  129. }
  130. };
  131. /**
  132. * Parses the given JSON schema and resolves any JSON references, including references in
  133. * externally-referenced files.
  134. *
  135. * @param {string} [path] - The file path or URL of the JSON schema
  136. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  137. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed and resolved
  138. * @param {function} [callback]
  139. * - An error-first callback. The second parameter is a {@link $Refs} object containing the resolved JSON references
  140. *
  141. * @returns {Promise}
  142. * The returned promise resolves with a {@link $Refs} object containing the resolved JSON references
  143. */
  144. $RefParser.resolve = function resolve (path, schema, options, callback) {
  145. let Class = this; // eslint-disable-line consistent-this
  146. let instance = new Class();
  147. return instance.resolve.apply(instance, arguments);
  148. };
  149. /**
  150. * Parses the given JSON schema and resolves any JSON references, including references in
  151. * externally-referenced files.
  152. *
  153. * @param {string} [path] - The file path or URL of the JSON schema
  154. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  155. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed and resolved
  156. * @param {function} [callback]
  157. * - An error-first callback. The second parameter is a {@link $Refs} object containing the resolved JSON references
  158. *
  159. * @returns {Promise}
  160. * The returned promise resolves with a {@link $Refs} object containing the resolved JSON references
  161. */
  162. $RefParser.prototype.resolve = async function resolve (path, schema, options, callback) {
  163. let me = this;
  164. let args = normalizeArgs(arguments);
  165. try {
  166. await this.parse(args.path, args.schema, args.options);
  167. await resolveExternal(me, args.options);
  168. finalize(me);
  169. return maybe(args.callback, Promise.resolve(me.$refs));
  170. }
  171. catch (err) {
  172. return maybe(args.callback, Promise.reject(err));
  173. }
  174. };
  175. /**
  176. * Parses the given JSON schema, resolves any JSON references, and bundles all external references
  177. * into the main JSON schema. This produces a JSON schema that only has *internal* references,
  178. * not any *external* references.
  179. *
  180. * @param {string} [path] - The file path or URL of the JSON schema
  181. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  182. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced
  183. * @param {function} [callback] - An error-first callback. The second parameter is the bundled JSON schema object
  184. * @returns {Promise} - The returned promise resolves with the bundled JSON schema object.
  185. */
  186. $RefParser.bundle = function bundle (path, schema, options, callback) {
  187. let Class = this; // eslint-disable-line consistent-this
  188. let instance = new Class();
  189. return instance.bundle.apply(instance, arguments);
  190. };
  191. /**
  192. * Parses the given JSON schema, resolves any JSON references, and bundles all external references
  193. * into the main JSON schema. This produces a JSON schema that only has *internal* references,
  194. * not any *external* references.
  195. *
  196. * @param {string} [path] - The file path or URL of the JSON schema
  197. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  198. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced
  199. * @param {function} [callback] - An error-first callback. The second parameter is the bundled JSON schema object
  200. * @returns {Promise} - The returned promise resolves with the bundled JSON schema object.
  201. */
  202. $RefParser.prototype.bundle = async function bundle (path, schema, options, callback) {
  203. let me = this;
  204. let args = normalizeArgs(arguments);
  205. try {
  206. await this.resolve(args.path, args.schema, args.options);
  207. _bundle(me, args.options);
  208. finalize(me);
  209. return maybe(args.callback, Promise.resolve(me.schema));
  210. }
  211. catch (err) {
  212. return maybe(args.callback, Promise.reject(err));
  213. }
  214. };
  215. /**
  216. * Parses the given JSON schema, resolves any JSON references, and dereferences the JSON schema.
  217. * That is, all JSON references are replaced with their resolved values.
  218. *
  219. * @param {string} [path] - The file path or URL of the JSON schema
  220. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  221. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced
  222. * @param {function} [callback] - An error-first callback. The second parameter is the dereferenced JSON schema object
  223. * @returns {Promise} - The returned promise resolves with the dereferenced JSON schema object.
  224. */
  225. $RefParser.dereference = function dereference (path, schema, options, callback) {
  226. let Class = this; // eslint-disable-line consistent-this
  227. let instance = new Class();
  228. return instance.dereference.apply(instance, arguments);
  229. };
  230. /**
  231. * Parses the given JSON schema, resolves any JSON references, and dereferences the JSON schema.
  232. * That is, all JSON references are replaced with their resolved values.
  233. *
  234. * @param {string} [path] - The file path or URL of the JSON schema
  235. * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`.
  236. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced
  237. * @param {function} [callback] - An error-first callback. The second parameter is the dereferenced JSON schema object
  238. * @returns {Promise} - The returned promise resolves with the dereferenced JSON schema object.
  239. */
  240. $RefParser.prototype.dereference = async function dereference (path, schema, options, callback) {
  241. let me = this;
  242. let args = normalizeArgs(arguments);
  243. try {
  244. await this.resolve(args.path, args.schema, args.options);
  245. _dereference(me, args.options);
  246. finalize(me);
  247. return maybe(args.callback, Promise.resolve(me.schema));
  248. }
  249. catch (err) {
  250. return maybe(args.callback, Promise.reject(err));
  251. }
  252. };
  253. function finalize (parser) {
  254. const errors = JSONParserErrorGroup.getParserErrors(parser);
  255. if (errors.length > 0) {
  256. throw new JSONParserErrorGroup(parser);
  257. }
  258. }