index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /* eslint-disable no-unused-vars */
  2. "use strict";
  3. const validateSchema = require("./validators/schema");
  4. const validateSpec = require("./validators/spec");
  5. const normalizeArgs = require("@apidevtools/json-schema-ref-parser/lib/normalize-args");
  6. const util = require("./util");
  7. const Options = require("./options");
  8. const maybe = require("call-me-maybe");
  9. const { ono } = require("@jsdevtools/ono");
  10. const $RefParser = require("@apidevtools/json-schema-ref-parser");
  11. const dereference = require("@apidevtools/json-schema-ref-parser/lib/dereference");
  12. module.exports = SwaggerParser;
  13. /**
  14. * This class parses a Swagger 2.0 or 3.0 API, resolves its JSON references and their resolved values,
  15. * and provides methods for traversing, dereferencing, and validating the API.
  16. *
  17. * @class
  18. * @augments $RefParser
  19. */
  20. function SwaggerParser () {
  21. $RefParser.apply(this, arguments);
  22. }
  23. util.inherits(SwaggerParser, $RefParser);
  24. SwaggerParser.parse = $RefParser.parse;
  25. SwaggerParser.resolve = $RefParser.resolve;
  26. SwaggerParser.bundle = $RefParser.bundle;
  27. SwaggerParser.dereference = $RefParser.dereference;
  28. /**
  29. * Alias {@link $RefParser#schema} as {@link SwaggerParser#api}
  30. */
  31. Object.defineProperty(SwaggerParser.prototype, "api", {
  32. configurable: true,
  33. enumerable: true,
  34. get () {
  35. return this.schema;
  36. }
  37. });
  38. /**
  39. * Parses the given Swagger API.
  40. * This method does not resolve any JSON references.
  41. * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
  42. *
  43. * @param {string} [path] - The file path or URL of the JSON schema
  44. * @param {object} [api] - The Swagger API object. This object will be used instead of reading from `path`.
  45. * @param {ParserOptions} [options] - Options that determine how the API is parsed
  46. * @param {Function} [callback] - An error-first callback. The second parameter is the parsed API object.
  47. * @returns {Promise} - The returned promise resolves with the parsed API object.
  48. */
  49. SwaggerParser.prototype.parse = async function (path, api, options, callback) {
  50. let args = normalizeArgs(arguments);
  51. args.options = new Options(args.options);
  52. try {
  53. let schema = await $RefParser.prototype.parse.call(this, args.path, args.schema, args.options);
  54. if (schema.swagger) {
  55. // Verify that the parsed object is a Swagger API
  56. if (schema.swagger === undefined || schema.info === undefined || schema.paths === undefined) {
  57. throw ono.syntax(`${args.path || args.schema} is not a valid Swagger API definition`);
  58. }
  59. else if (typeof schema.swagger === "number") {
  60. // This is a very common mistake, so give a helpful error message
  61. throw ono.syntax('Swagger version number must be a string (e.g. "2.0") not a number.');
  62. }
  63. else if (typeof schema.info.version === "number") {
  64. // This is a very common mistake, so give a helpful error message
  65. throw ono.syntax('API version number must be a string (e.g. "1.0.0") not a number.');
  66. }
  67. else if (schema.swagger !== "2.0") {
  68. throw ono.syntax(`Unrecognized Swagger version: ${schema.swagger}. Expected 2.0`);
  69. }
  70. }
  71. else {
  72. let supportedVersions = ["3.0.0", "3.0.1", "3.0.2", "3.0.3"];
  73. // Verify that the parsed object is a Openapi API
  74. if (schema.openapi === undefined || schema.info === undefined || schema.paths === undefined) {
  75. throw ono.syntax(`${args.path || args.schema} is not a valid Openapi API definition`);
  76. }
  77. else if (typeof schema.openapi === "number") {
  78. // This is a very common mistake, so give a helpful error message
  79. throw ono.syntax('Openapi version number must be a string (e.g. "3.0.0") not a number.');
  80. }
  81. else if (typeof schema.info.version === "number") {
  82. // This is a very common mistake, so give a helpful error message
  83. throw ono.syntax('API version number must be a string (e.g. "1.0.0") not a number.');
  84. }
  85. else if (supportedVersions.indexOf(schema.openapi) === -1) {
  86. throw ono.syntax(
  87. `Unsupported OpenAPI version: ${schema.openapi}. ` +
  88. `Swagger Parser only supports versions ${supportedVersions.join(", ")}`
  89. );
  90. }
  91. }
  92. // Looks good!
  93. return maybe(args.callback, Promise.resolve(schema));
  94. }
  95. catch (err) {
  96. return maybe(args.callback, Promise.reject(err));
  97. }
  98. };
  99. /**
  100. * Parses, dereferences, and validates the given Swagger API.
  101. * Depending on the options, validation can include JSON Schema validation and/or Swagger Spec validation.
  102. *
  103. * @param {string} [path] - The file path or URL of the JSON schema
  104. * @param {object} [api] - The Swagger API object. This object will be used instead of reading from `path`.
  105. * @param {ParserOptions} [options] - Options that determine how the API is parsed, dereferenced, and validated
  106. * @param {Function} [callback] - An error-first callback. The second parameter is the parsed API object.
  107. * @returns {Promise} - The returned promise resolves with the parsed API object.
  108. */
  109. SwaggerParser.validate = function (path, api, options, callback) {
  110. let Class = this; // eslint-disable-line consistent-this
  111. let instance = new Class();
  112. return instance.validate.apply(instance, arguments);
  113. };
  114. /**
  115. * Parses, dereferences, and validates the given Swagger API.
  116. * Depending on the options, validation can include JSON Schema validation and/or Swagger Spec validation.
  117. *
  118. * @param {string} [path] - The file path or URL of the JSON schema
  119. * @param {object} [api] - The Swagger API object. This object will be used instead of reading from `path`.
  120. * @param {ParserOptions} [options] - Options that determine how the API is parsed, dereferenced, and validated
  121. * @param {Function} [callback] - An error-first callback. The second parameter is the parsed API object.
  122. * @returns {Promise} - The returned promise resolves with the parsed API object.
  123. */
  124. SwaggerParser.prototype.validate = async function (path, api, options, callback) {
  125. let me = this;
  126. let args = normalizeArgs(arguments);
  127. args.options = new Options(args.options);
  128. // ZSchema doesn't support circular objects, so don't dereference circular $refs yet
  129. // (see https://github.com/zaggino/z-schema/issues/137)
  130. let circular$RefOption = args.options.dereference.circular;
  131. args.options.validate.schema && (args.options.dereference.circular = "ignore");
  132. try {
  133. await this.dereference(args.path, args.schema, args.options);
  134. // Restore the original options, now that we're done dereferencing
  135. args.options.dereference.circular = circular$RefOption;
  136. if (args.options.validate.schema) {
  137. // Validate the API against the Swagger schema
  138. // NOTE: This is safe to do, because we haven't dereferenced circular $refs yet
  139. validateSchema(me.api);
  140. if (me.$refs.circular) {
  141. if (circular$RefOption === true) {
  142. // The API has circular references,
  143. // so we need to do a second-pass to fully-dereference it
  144. dereference(me, args.options);
  145. }
  146. else if (circular$RefOption === false) {
  147. // The API has circular references, and they're not allowed, so throw an error
  148. throw ono.reference("The API contains circular references");
  149. }
  150. }
  151. }
  152. if (args.options.validate.spec) {
  153. // Validate the API against the Swagger spec
  154. validateSpec(me.api);
  155. }
  156. return maybe(args.callback, Promise.resolve(me.schema));
  157. }
  158. catch (err) {
  159. return maybe(args.callback, Promise.reject(err));
  160. }
  161. };
  162. /**
  163. * The Swagger object
  164. * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
  165. *
  166. * @typedef {{swagger: string, info: {}, paths: {}}} SwaggerObject
  167. */