resolve-external.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. "use strict";
  2. const $Ref = require("./ref");
  3. const Pointer = require("./pointer");
  4. const parse = require("./parse");
  5. const url = require("./util/url");
  6. const { isHandledError } = require("./util/errors");
  7. module.exports = resolveExternal;
  8. /**
  9. * Crawls the JSON schema, finds all external JSON references, and resolves their values.
  10. * This method does not mutate the JSON schema. The resolved values are added to {@link $RefParser#$refs}.
  11. *
  12. * NOTE: We only care about EXTERNAL references here. INTERNAL references are only relevant when dereferencing.
  13. *
  14. * @param {$RefParser} parser
  15. * @param {$RefParserOptions} options
  16. *
  17. * @returns {Promise}
  18. * The promise resolves once all JSON references in the schema have been resolved,
  19. * including nested references that are contained in externally-referenced files.
  20. */
  21. function resolveExternal (parser, options) {
  22. if (!options.resolve.external) {
  23. // Nothing to resolve, so exit early
  24. return Promise.resolve();
  25. }
  26. try {
  27. // console.log('Resolving $ref pointers in %s', parser.$refs._root$Ref.path);
  28. let promises = crawl(parser.schema, parser.$refs._root$Ref.path + "#", parser.$refs, options);
  29. return Promise.all(promises);
  30. }
  31. catch (e) {
  32. return Promise.reject(e);
  33. }
  34. }
  35. /**
  36. * Recursively crawls the given value, and resolves any external JSON references.
  37. *
  38. * @param {*} obj - The value to crawl. If it's not an object or array, it will be ignored.
  39. * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash
  40. * @param {$Refs} $refs
  41. * @param {$RefParserOptions} options
  42. * @param {Set} seen - Internal.
  43. *
  44. * @returns {Promise[]}
  45. * Returns an array of promises. There will be one promise for each JSON reference in `obj`.
  46. * If `obj` does not contain any JSON references, then the array will be empty.
  47. * If any of the JSON references point to files that contain additional JSON references,
  48. * then the corresponding promise will internally reference an array of promises.
  49. */
  50. function crawl (obj, path, $refs, options, seen) {
  51. seen = seen || new Set();
  52. let promises = [];
  53. if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
  54. seen.add(obj); // Track previously seen objects to avoid infinite recursion
  55. if ($Ref.isExternal$Ref(obj)) {
  56. promises.push(resolve$Ref(obj, path, $refs, options));
  57. }
  58. else {
  59. for (let key of Object.keys(obj)) {
  60. let keyPath = Pointer.join(path, key);
  61. let value = obj[key];
  62. if ($Ref.isExternal$Ref(value)) {
  63. promises.push(resolve$Ref(value, keyPath, $refs, options));
  64. }
  65. else {
  66. promises = promises.concat(crawl(value, keyPath, $refs, options, seen));
  67. }
  68. }
  69. }
  70. }
  71. return promises;
  72. }
  73. /**
  74. * Resolves the given JSON Reference, and then crawls the resulting value.
  75. *
  76. * @param {{$ref: string}} $ref - The JSON Reference to resolve
  77. * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash
  78. * @param {$Refs} $refs
  79. * @param {$RefParserOptions} options
  80. *
  81. * @returns {Promise}
  82. * The promise resolves once all JSON references in the object have been resolved,
  83. * including nested references that are contained in externally-referenced files.
  84. */
  85. async function resolve$Ref ($ref, path, $refs, options) {
  86. // console.log('Resolving $ref pointer "%s" at %s', $ref.$ref, path);
  87. let resolvedPath = url.resolve(path, $ref.$ref);
  88. let withoutHash = url.stripHash(resolvedPath);
  89. // Do we already have this $ref?
  90. $ref = $refs._$refs[withoutHash];
  91. if ($ref) {
  92. // We've already parsed this $ref, so use the existing value
  93. return Promise.resolve($ref.value);
  94. }
  95. // Parse the $referenced file/url
  96. try {
  97. const result = await parse(resolvedPath, $refs, options);
  98. // Crawl the parsed value
  99. // console.log('Resolving $ref pointers in %s', withoutHash);
  100. let promises = crawl(result, withoutHash + "#", $refs, options);
  101. return Promise.all(promises);
  102. }
  103. catch (err) {
  104. if (!options.continueOnError || !isHandledError(err)) {
  105. throw err;
  106. }
  107. if ($refs._$refs[withoutHash]) {
  108. err.source = decodeURI(url.stripHash(path));
  109. err.path = url.safePointerToPath(url.getHash(path));
  110. }
  111. return [];
  112. }
  113. }