@apidevtools/swagger-parser
Version:
Swagger 2.0 and OpenAPI 3.0 parser and validator for Node and browsers
207 lines (184 loc) • 8.27 kB
JavaScript
/* eslint-disable no-unused-vars */
;
const validateSchema = require("./validators/schema");
const validateSpec = require("./validators/spec");
const { jsonSchemaParserNormalizeArgs: normalizeArgs } = require("@apidevtools/json-schema-ref-parser");
const util = require("./util");
const Options = require("./options");
const maybe = require("call-me-maybe");
const { ono } = require("@jsdevtools/ono");
const { $RefParser } = require("@apidevtools/json-schema-ref-parser");
const { dereferenceInternal: dereference } = require("@apidevtools/json-schema-ref-parser");
const supported31Versions = ["3.1.0", "3.1.1"];
const supported30Versions = ["3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4"];
const supportedVersions = [...supported31Versions, ...supported30Versions];
/**
* This class parses a Swagger 2.0 or 3.0 API, resolves its JSON references and their resolved values,
* and provides methods for traversing, dereferencing, and validating the API.
*
* @class
* @augments $RefParser
*/
class SwaggerParser extends $RefParser {
/**
* Parses the given Swagger API.
* This method does not resolve any JSON references.
* It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
*
* @param {string} [path] - The file path or URL of the JSON schema
* @param {object} [api] - The Swagger API object. This object will be used instead of reading from `path`.
* @param {ParserOptions} [options] - Options that determine how the API is parsed
* @param {Function} [callback] - An error-first callback. The second parameter is the parsed API object.
* @returns {Promise} - The returned promise resolves with the parsed API object.
*/
async parse (path, api, options, callback) {
let args = normalizeArgs(arguments);
args.options = new Options(args.options);
try {
let schema = await super.parse(args.path, args.schema, args.options);
if (schema.swagger) {
// Verify that the parsed object is a Swagger API
if (schema.swagger === undefined || schema.info === undefined || schema.paths === undefined) {
throw ono.syntax(`${args.path || args.schema} is not a valid Swagger API definition`);
}
else if (typeof schema.swagger === "number") {
// This is a very common mistake, so give a helpful error message
throw ono.syntax('Swagger version number must be a string (e.g. "2.0") not a number.');
}
else if (typeof schema.info.version === "number") {
// This is a very common mistake, so give a helpful error message
throw ono.syntax('API version number must be a string (e.g. "1.0.0") not a number.');
}
else if (schema.swagger !== "2.0") {
throw ono.syntax(`Unrecognized Swagger version: ${schema.swagger}. Expected 2.0`);
}
}
else {
// Verify that the parsed object is a Openapi API
if (schema.openapi === undefined || schema.info === undefined) {
throw ono.syntax(`${args.path || args.schema} is not a valid Openapi API definition`);
}
else if (schema.paths === undefined) {
if (supported31Versions.indexOf(schema.openapi) !== -1) {
if (schema.webhooks === undefined) {
throw ono.syntax(`${args.path || args.schema} is not a valid Openapi API definition`);
}
}
else {
throw ono.syntax(`${args.path || args.schema} is not a valid Openapi API definition`);
}
}
else if (typeof schema.openapi === "number") {
// This is a very common mistake, so give a helpful error message
throw ono.syntax('Openapi version number must be a string (e.g. "3.0.0") not a number.');
}
else if (typeof schema.info.version === "number") {
// This is a very common mistake, so give a helpful error message
throw ono.syntax('API version number must be a string (e.g. "1.0.0") not a number.');
}
else if (supportedVersions.indexOf(schema.openapi) === -1) {
throw ono.syntax(
`Unsupported OpenAPI version: ${schema.openapi}. ` +
`Swagger Parser only supports versions ${supportedVersions.join(", ")}`
);
}
// This is an OpenAPI v3 schema, check if the "servers" have any relative paths and
// fix them if the content was pulled from a web resource
util.fixOasRelativeServers(schema, args.path);
}
// Looks good!
return maybe(args.callback, Promise.resolve(schema));
}
catch (err) {
return maybe(args.callback, Promise.reject(err));
}
}
/**
* Parses, dereferences, and validates the given Swagger API.
* Depending on the options, validation can include JSON Schema validation and/or Swagger Spec validation.
*
* @param {string} [path] - The file path or URL of the JSON schema
* @param {object} [api] - The Swagger API object. This object will be used instead of reading from `path`.
* @param {ParserOptions} [options] - Options that determine how the API is parsed, dereferenced, and validated
* @param {Function} [callback] - An error-first callback. The second parameter is the parsed API object.
* @returns {Promise} - The returned promise resolves with the parsed API object.
*/
async validate (path, api, options, callback) {
let me = this;
let args = normalizeArgs(arguments);
args.options = new Options(args.options);
// ZSchema doesn't support circular objects, so don't dereference circular $refs yet
// (see https://github.com/zaggino/z-schema/issues/137)
let circular$RefOption = args.options.dereference.circular;
args.options.validate.schema && (args.options.dereference.circular = "ignore");
try {
await this.dereference(args.path, args.schema, args.options);
// Restore the original options, now that we're done dereferencing
args.options.dereference.circular = circular$RefOption;
if (args.options.validate.schema) {
// Validate the API against the Swagger schema
// NOTE: This is safe to do, because we haven't dereferenced circular $refs yet
validateSchema(me.api);
if (me.$refs.circular) {
if (circular$RefOption === true) {
// The API has circular references,
// so we need to do a second-pass to fully-dereference it
dereference(me, args.options);
}
else if (circular$RefOption === false) {
// The API has circular references, and they're not allowed, so throw an error
throw ono.reference("The API contains circular references");
}
}
}
if (args.options.validate.spec) {
// Validate the API against the Swagger spec
validateSpec(me.api);
}
return maybe(args.callback, Promise.resolve(me.schema));
}
catch (err) {
return maybe(args.callback, Promise.reject(err));
}
}
}
/**
* Alias {@link $RefParser#schema} as {@link SwaggerParser#api}
*/
Object.defineProperty(SwaggerParser.prototype, "api", {
configurable: true,
enumerable: true,
get () {
return this.schema;
}
});
/**
* The Swagger object
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object
*
* @typedef {{swagger: string, info: {}, paths: {}}} SwaggerObject
*/
const defaultExport = SwaggerParser;
defaultExport.validate = (...args) => {
const defaultInstance = new SwaggerParser();
return defaultInstance.validate(...args);
};
defaultExport.dereference = (...args) => {
const defaultInstance = new SwaggerParser();
return defaultInstance.dereference(...args);
};
defaultExport.bundle = (...args) => {
const defaultInstance = new SwaggerParser();
return defaultInstance.bundle(...args);
};
defaultExport.parse = (...args) => {
const defaultInstance = new SwaggerParser();
return defaultInstance.parse(...args);
};
defaultExport.resolve = (...args) => {
const defaultInstance = new SwaggerParser();
return defaultInstance.resolve(...args);
};
defaultExport.default = defaultExport;
defaultExport.SwaggerParser = defaultExport;
module.exports = defaultExport;