@readme/openapi-parser
Advanced tools
+834
| "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/index.ts | ||
| var _jsonschemarefparser = require('@apidevtools/json-schema-ref-parser'); var _jsonschemarefparser2 = _interopRequireDefault(_jsonschemarefparser); | ||
| // src/lib/index.ts | ||
| var pathParameterTemplateRegExp = /\{([^/}]+)}/g; | ||
| var supportedHTTPMethods = ["get", "post", "put", "delete", "patch", "options", "head", "trace"]; | ||
| var swaggerHTTPMethods = ["get", "put", "post", "delete", "options", "head", "patch"]; | ||
| function isSwagger(schema) { | ||
| return "swagger" in schema && schema.swagger !== void 0; | ||
| } | ||
| function isOpenAPI(schema) { | ||
| return "openapi" in schema && schema.openapi !== void 0; | ||
| } | ||
| function isOpenAPI30(schema) { | ||
| return "openapi" in schema && schema.openapi !== void 0 && schema.openapi.startsWith("3.0"); | ||
| } | ||
| function isOpenAPI31(schema) { | ||
| return "openapi" in schema && schema.openapi !== void 0 && schema.openapi.startsWith("3.1"); | ||
| } | ||
| function getSpecificationName(api) { | ||
| return isSwagger(api) ? "Swagger" : "OpenAPI"; | ||
| } | ||
| // src/util.ts | ||
| // src/repair.ts | ||
| function fixServers(server, path) { | ||
| if (server && "url" in server && server.url && server.url.startsWith("/")) { | ||
| try { | ||
| const inUrl = new URL(path); | ||
| server.url = `${inUrl.protocol}//${inUrl.hostname}${server.url}`; | ||
| } catch (e) { | ||
| } | ||
| } | ||
| } | ||
| function fixOasRelativeServers(schema, filePath) { | ||
| if (!schema || !isOpenAPI(schema) || !filePath || !filePath.startsWith("http:") && !filePath.startsWith("https:")) { | ||
| return; | ||
| } | ||
| if (schema.servers) { | ||
| schema.servers.map((server) => fixServers(server, filePath)); | ||
| } | ||
| ["paths", "webhooks"].forEach((component) => { | ||
| if (component in schema) { | ||
| const schemaElement = schema.paths || {}; | ||
| Object.keys(schemaElement).forEach((path) => { | ||
| const pathItem = schemaElement[path] || {}; | ||
| Object.keys(pathItem).forEach((opItem) => { | ||
| const pathItemElement = pathItem[opItem]; | ||
| if (!pathItemElement) { | ||
| return; | ||
| } | ||
| if (opItem === "servers" && Array.isArray(pathItemElement)) { | ||
| pathItemElement.forEach((server) => fixServers(server, filePath)); | ||
| return; | ||
| } | ||
| if (supportedHTTPMethods.includes(opItem) && typeof pathItemElement === "object" && "servers" in pathItemElement && Array.isArray(pathItemElement.servers)) { | ||
| pathItemElement.servers.forEach((server) => fixServers(server, filePath)); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| // src/util.ts | ||
| function repairSchema(schema, filePath) { | ||
| if (isOpenAPI(schema)) { | ||
| fixOasRelativeServers(schema, filePath); | ||
| } | ||
| } | ||
| function normalizeArguments(api) { | ||
| return { | ||
| path: typeof api === "string" ? api : "", | ||
| schema: typeof api === "object" ? api : void 0 | ||
| }; | ||
| } | ||
| function convertOptionsForParser(options) { | ||
| const parserOptions = _jsonschemarefparser.getJsonSchemaRefParserDefaultOptions.call(void 0, ); | ||
| return { | ||
| ...parserOptions, | ||
| dereference: { | ||
| ...parserOptions.dereference, | ||
| circular: _optionalChain([options, 'optionalAccess', _ => _.dereference]) && "circular" in options.dereference ? options.dereference.circular : parserOptions.dereference.circular, | ||
| onCircular: _optionalChain([options, 'optionalAccess', _2 => _2.dereference, 'optionalAccess', _3 => _3.onCircular]) || parserOptions.dereference.onCircular, | ||
| onDereference: _optionalChain([options, 'optionalAccess', _4 => _4.dereference, 'optionalAccess', _5 => _5.onDereference]) || parserOptions.dereference.onDereference, | ||
| // OpenAPI 3.1 allows for `summary` and `description` properties at the same level as a `$ref` | ||
| // pointer to be preserved when that `$ref` pointer is dereferenced. The default behavior of | ||
| // `json-schema-ref-parser` is to discard these properties but this option allows us to | ||
| // override that behavior. | ||
| preservedProperties: ["summary", "description"] | ||
| }, | ||
| resolve: { | ||
| ...parserOptions.resolve, | ||
| external: _optionalChain([options, 'optionalAccess', _6 => _6.resolve]) && "external" in options.resolve ? options.resolve.external : parserOptions.resolve.external, | ||
| http: { | ||
| ...typeof parserOptions.resolve.http === "object" ? parserOptions.resolve.http : {}, | ||
| timeout: _optionalChain([options, 'optionalAccess', _7 => _7.resolve, 'optionalAccess', _8 => _8.http]) && "timeout" in options.resolve.http ? options.resolve.http.timeout : 5e3 | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| // src/validators/schema.ts | ||
| var _betterajverrors = require('@readme/better-ajv-errors'); var _betterajverrors2 = _interopRequireDefault(_betterajverrors); | ||
| var _openapischemas = require('@readme/openapi-schemas'); | ||
| var _2020js = require('ajv/dist/2020.js'); var _2020js2 = _interopRequireDefault(_2020js); | ||
| var _ajvdraft04 = require('ajv-draft-04'); var _ajvdraft042 = _interopRequireDefault(_ajvdraft04); | ||
| // src/lib/reduceAjvErrors.ts | ||
| function reduceAjvErrors(errors) { | ||
| const flattened = /* @__PURE__ */ new Map(); | ||
| errors.forEach((err) => { | ||
| if (["must have required property '$ref'", "must match exactly one schema in oneOf"].includes(err.message)) { | ||
| return; | ||
| } | ||
| if (!flattened.size) { | ||
| flattened.set(err.instancePath, err); | ||
| return; | ||
| } else if (flattened.has(err.instancePath)) { | ||
| return; | ||
| } | ||
| let shouldRecordError = true; | ||
| flattened.forEach((flat) => { | ||
| if (flat.instancePath.includes(err.instancePath)) { | ||
| shouldRecordError = false; | ||
| } | ||
| }); | ||
| if (shouldRecordError) { | ||
| flattened.set(err.instancePath, err); | ||
| } | ||
| }); | ||
| if (!flattened.size) { | ||
| return errors; | ||
| } | ||
| return [...flattened.values()]; | ||
| } | ||
| // src/validators/schema.ts | ||
| var LARGE_SPEC_ERROR_CAP = 20; | ||
| var LARGE_SPEC_SIZE_CAP = 5e6; | ||
| function initializeAjv(draft04 = true) { | ||
| const opts = { | ||
| allErrors: true, | ||
| strict: false, | ||
| validateFormats: false | ||
| }; | ||
| if (draft04) { | ||
| return new (0, _ajvdraft042.default)(opts); | ||
| } | ||
| return new (0, _2020js2.default)(opts); | ||
| } | ||
| function validateSchema(api, options = {}) { | ||
| let ajv; | ||
| let schema; | ||
| const specificationName = getSpecificationName(api); | ||
| if (isSwagger(api)) { | ||
| schema = _openapischemas.openapi.v2; | ||
| ajv = initializeAjv(); | ||
| } else if (isOpenAPI31(api)) { | ||
| schema = _openapischemas.openapi.v31legacy; | ||
| const schemaDynamicRef = schema.$defs.schema; | ||
| if ("$dynamicAnchor" in schemaDynamicRef) { | ||
| delete schemaDynamicRef.$dynamicAnchor; | ||
| } | ||
| schema.$defs.components.properties.schemas.additionalProperties = schemaDynamicRef; | ||
| schema.$defs.header.dependentSchemas.schema.properties.schema = schemaDynamicRef; | ||
| schema.$defs["media-type"].properties.schema = schemaDynamicRef; | ||
| schema.$defs.parameter.properties.schema = schemaDynamicRef; | ||
| ajv = initializeAjv(false); | ||
| } else { | ||
| schema = _openapischemas.openapi.v3; | ||
| ajv = initializeAjv(); | ||
| } | ||
| const isValid = ajv.validate(schema, api); | ||
| if (isValid) { | ||
| return { valid: true, warnings: [], specification: specificationName }; | ||
| } | ||
| let additionalErrors = 0; | ||
| let reducedErrors = reduceAjvErrors(ajv.errors); | ||
| if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) { | ||
| try { | ||
| if (JSON.stringify(api).length >= LARGE_SPEC_SIZE_CAP) { | ||
| additionalErrors = reducedErrors.length - 20; | ||
| reducedErrors = reducedErrors.slice(0, 20); | ||
| } | ||
| } catch (error) { | ||
| } | ||
| } | ||
| try { | ||
| const errors = _betterajverrors2.default.call(void 0, schema, api, reducedErrors, { | ||
| format: "cli-array", | ||
| colorize: _optionalChain([options, 'optionalAccess', _9 => _9.validate, 'optionalAccess', _10 => _10.errors, 'optionalAccess', _11 => _11.colorize]) || false, | ||
| indent: 2 | ||
| }); | ||
| return { | ||
| valid: false, | ||
| errors, | ||
| warnings: [], | ||
| additionalErrors, | ||
| specification: specificationName | ||
| }; | ||
| } catch (err) { | ||
| return { | ||
| valid: false, | ||
| errors: [{ message: err.message }], | ||
| warnings: [], | ||
| additionalErrors, | ||
| specification: specificationName | ||
| }; | ||
| } | ||
| } | ||
| // src/validators/spec/index.ts | ||
| var SpecificationValidator = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this); } | ||
| __init() {this.errors = []} | ||
| __init2() {this.warnings = []} | ||
| reportError(message) { | ||
| this.errors.push({ message }); | ||
| } | ||
| reportWarning(message) { | ||
| this.warnings.push({ message }); | ||
| } | ||
| }, _class); | ||
| // src/validators/spec/openapi.ts | ||
| var OpenAPISpecificationValidator = class extends SpecificationValidator { | ||
| constructor(api, rules) { | ||
| super(); | ||
| this.api = api; | ||
| this.rules = rules; | ||
| } | ||
| run() { | ||
| const operationIds = []; | ||
| Object.keys(this.api.paths || {}).forEach((pathName) => { | ||
| const path = this.api.paths[pathName]; | ||
| const pathId = `/paths${pathName}`; | ||
| if (path && pathName.startsWith("/")) { | ||
| this.validatePath(path, pathId, operationIds); | ||
| } | ||
| }); | ||
| if (isOpenAPI30(this.api)) { | ||
| if (this.api.components) { | ||
| Object.keys(this.api.components).forEach((componentType) => { | ||
| Object.keys(this.api.components[componentType]).forEach((componentName) => { | ||
| if (!/^[a-zA-Z0-9.\-_]+$/.test(componentName)) { | ||
| const componentId = `/components/${componentType}/${componentName}`; | ||
| this.reportError( | ||
| `\`${componentId}\` has an invalid name. Component names should match against: /^[a-zA-Z0-9.-_]+$/` | ||
| ); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| if (isOpenAPI31(this.api)) { | ||
| if (!Object.keys(this.api.paths || {}).length && !Object.keys(this.api.webhooks || {}).length) { | ||
| this.reportError("OpenAPI 3.1 definitions must contain at least one entry in either `paths` or `webhook`."); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given path. | ||
| * | ||
| */ | ||
| validatePath(path, pathId, operationIds) { | ||
| supportedHTTPMethods.forEach((operationName) => { | ||
| const operation = path[operationName]; | ||
| const operationId = `${pathId}/${operationName}`; | ||
| if (operation) { | ||
| const declaredOperationId = operation.operationId; | ||
| if (declaredOperationId) { | ||
| if (!operationIds.includes(declaredOperationId)) { | ||
| operationIds.push(declaredOperationId); | ||
| } else if (this.rules["duplicate-operation-id"] === "warning") { | ||
| this.reportWarning(`The operationId \`${declaredOperationId}\` is duplicated and should be made unique.`); | ||
| } else { | ||
| this.reportError(`The operationId \`${declaredOperationId}\` is duplicated and must be made unique.`); | ||
| } | ||
| } | ||
| this.validateParameters(path, pathId, operation, operationId); | ||
| Object.keys(operation.responses || {}).forEach((responseCode) => { | ||
| const response = operation.responses[responseCode]; | ||
| const responseId = `${operationId}/responses/${responseCode}`; | ||
| if (response && !("$ref" in response)) { | ||
| this.validateResponse(response, responseId); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameters(path, pathId, operation, operationId) { | ||
| const pathParams = path.parameters || []; | ||
| const operationParams = operation.parameters || []; | ||
| this.checkForDuplicates(pathParams, pathId); | ||
| this.checkForDuplicates(operationParams, operationId); | ||
| const params = pathParams.reduce((combinedParams, value) => { | ||
| const duplicate = combinedParams.some((param) => { | ||
| if ("$ref" in param || "$ref" in value) { | ||
| return false; | ||
| } | ||
| return param.in === value.in && param.name === value.name; | ||
| }); | ||
| if (!duplicate) { | ||
| combinedParams.push(value); | ||
| } | ||
| return combinedParams; | ||
| }, operationParams.slice()); | ||
| this.validatePathParameters(params, pathId, operationId); | ||
| this.validateParameterTypes(params, operationId); | ||
| } | ||
| /** | ||
| * Validates path parameters for the given path. | ||
| * | ||
| */ | ||
| validatePathParameters(params, pathId, operationId) { | ||
| const placeholders = [...new Set(pathId.match(pathParameterTemplateRegExp) || [])]; | ||
| params.filter((param) => "in" in param).filter((param) => param.in === "path").forEach((param) => { | ||
| if (param.required !== true) { | ||
| if (this.rules["non-optional-path-parameters"] === "warning") { | ||
| this.reportWarning( | ||
| `Path parameters should not be optional. Set \`required=true\` for the \`${param.name}\` parameter at \`${operationId}\`.` | ||
| ); | ||
| } else { | ||
| this.reportError( | ||
| `Path parameters cannot be optional. Set \`required=true\` for the \`${param.name}\` parameter at \`${operationId}\`.` | ||
| ); | ||
| } | ||
| } | ||
| const match = placeholders.indexOf(`{${param.name}}`); | ||
| if (match === -1) { | ||
| const error = `\`${operationId}\` has a path parameter named \`${param.name}\`, but there is no corresponding \`{${param.name}}\` in the path string.`; | ||
| if (this.rules["path-parameters-not-in-path"] === "warning") { | ||
| this.reportWarning(error); | ||
| } else { | ||
| this.reportError(error); | ||
| } | ||
| } | ||
| placeholders.splice(match, 1); | ||
| }); | ||
| if (placeholders.length > 0) { | ||
| const list = new Intl.ListFormat("en", { style: "long", type: "conjunction" }).format( | ||
| placeholders.map((placeholder) => `\`${placeholder}\``) | ||
| ); | ||
| const error = `\`${operationId}\` is missing path parameter(s) for ${list}.`; | ||
| if (this.rules["path-parameters-not-in-parameters"] === "warning") { | ||
| this.reportWarning(error); | ||
| } else { | ||
| this.reportError(error); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates data types of parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameterTypes(params, operationId) { | ||
| params.forEach((param) => { | ||
| if ("$ref" in param) { | ||
| return; | ||
| } | ||
| if (!param.schema && param.content) { | ||
| return; | ||
| } else if ("$ref" in param.schema) { | ||
| return; | ||
| } | ||
| const parameterId = `${operationId}/parameters/${param.name}`; | ||
| this.validateSchema(param.schema, parameterId); | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given response object. | ||
| * | ||
| */ | ||
| validateResponse(response, responseId) { | ||
| Object.keys(response.headers || {}).forEach((headerName) => { | ||
| const header = response.headers[headerName]; | ||
| const headerId = `${responseId}/headers/${headerName}`; | ||
| if ("$ref" in header) { | ||
| return; | ||
| } | ||
| if (header.schema) { | ||
| if (!("$ref" in header.schema)) { | ||
| this.validateSchema(header.schema, headerId); | ||
| } | ||
| } else if (header.content) { | ||
| Object.keys(header.content).forEach((mediaType) => { | ||
| if (header.content[mediaType].schema) { | ||
| if (!("$ref" in header.content[mediaType].schema)) { | ||
| this.validateSchema(header.content[mediaType].schema || {}, `${headerId}/content/${mediaType}/schema`); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| if (response.content) { | ||
| Object.keys(response.content).forEach((mediaType) => { | ||
| if (response.content[mediaType].schema) { | ||
| if (!("$ref" in response.content[mediaType].schema)) { | ||
| this.validateSchema(response.content[mediaType].schema || {}, `${responseId}/content/${mediaType}/schema`); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given Swagger schema object. | ||
| * | ||
| */ | ||
| validateSchema(schema, schemaId) { | ||
| if (schema.type === "array" && !schema.items) { | ||
| if (this.rules["array-without-items"] === "warning") { | ||
| this.reportWarning(`\`${schemaId}\` is an array, so it should include an \`items\` schema.`); | ||
| } else { | ||
| this.reportError(`\`${schemaId}\` is an array, so it must include an \`items\` schema.`); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Checks the given parameter list for duplicates. | ||
| * | ||
| */ | ||
| checkForDuplicates(params, schemaId) { | ||
| for (let i = 0; i < params.length - 1; i++) { | ||
| const outer = params[i]; | ||
| for (let j = i + 1; j < params.length; j++) { | ||
| const inner = params[j]; | ||
| if ("$ref" in outer || "$ref" in inner) { | ||
| continue; | ||
| } | ||
| if (outer.name === inner.name && outer.in === inner.in) { | ||
| const error = `Found multiple \`${outer.in}\` parameters named \`${outer.name}\` in \`${schemaId}\`.`; | ||
| if (this.rules["duplicate-non-request-body-parameters"] === "warning") { | ||
| this.reportWarning(error); | ||
| } else { | ||
| this.reportError(error); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| // src/validators/spec/swagger.ts | ||
| var SwaggerSpecificationValidator = class extends SpecificationValidator { | ||
| constructor(api) { | ||
| super(); | ||
| this.api = api; | ||
| } | ||
| run() { | ||
| const operationIds = []; | ||
| Object.keys(this.api.paths || {}).forEach((pathName) => { | ||
| const path = this.api.paths[pathName]; | ||
| const pathId = `/paths${pathName}`; | ||
| if (path && pathName.startsWith("/")) { | ||
| this.validatePath(path, pathId, operationIds); | ||
| } | ||
| }); | ||
| Object.keys(this.api.definitions || {}).forEach((definitionName) => { | ||
| const definition = this.api.definitions[definitionName]; | ||
| const definitionId = `/definitions/${definitionName}`; | ||
| if (!/^[a-zA-Z0-9.\-_]+$/.test(definitionName)) { | ||
| this.reportError( | ||
| `\`${definitionId}\` has an invalid name. Definition names should match against: /^[a-zA-Z0-9.-_]+$/` | ||
| ); | ||
| } | ||
| this.validateRequiredPropertiesExist(definition, definitionId); | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given path. | ||
| * | ||
| */ | ||
| validatePath(path, pathId, operationIds) { | ||
| swaggerHTTPMethods.forEach((operationName) => { | ||
| const operation = path[operationName]; | ||
| const operationId = `${pathId}/${operationName}`; | ||
| if (operation) { | ||
| const declaredOperationId = operation.operationId; | ||
| if (declaredOperationId) { | ||
| if (!operationIds.includes(declaredOperationId)) { | ||
| operationIds.push(declaredOperationId); | ||
| } else { | ||
| this.reportError(`The operationId \`${declaredOperationId}\` is duplicated and must be made unique.`); | ||
| } | ||
| } | ||
| this.validateParameters(path, pathId, operation, operationId); | ||
| Object.keys(operation.responses || {}).forEach((responseName) => { | ||
| const response = operation.responses[responseName]; | ||
| if ("$ref" in response || !response) { | ||
| return; | ||
| } | ||
| const responseId = `${operationId}/responses/${responseName}`; | ||
| this.validateResponse(responseName, response, responseId); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameters(path, pathId, operation, operationId) { | ||
| const pathParams = (path.parameters || []).filter((param) => !("$ref" in param)); | ||
| const operationParams = (operation.parameters || []).filter( | ||
| (param) => !("$ref" in param) | ||
| ); | ||
| this.checkForDuplicates(pathParams, pathId); | ||
| this.checkForDuplicates(operationParams, operationId); | ||
| const params = pathParams.reduce((combinedParams, value) => { | ||
| const duplicate = combinedParams.some((param) => { | ||
| if ("$ref" in param || "$ref" in value) { | ||
| return false; | ||
| } | ||
| return param.in === value.in && param.name === value.name; | ||
| }); | ||
| if (!duplicate) { | ||
| combinedParams.push(value); | ||
| } | ||
| return combinedParams; | ||
| }, operationParams.slice()); | ||
| this.validateBodyParameters(params, operationId); | ||
| this.validatePathParameters(params, pathId, operationId); | ||
| this.validateParameterTypes(params, operation, operationId); | ||
| } | ||
| /** | ||
| * Validates body and formData parameters for the given operation. | ||
| * | ||
| */ | ||
| validateBodyParameters(params, operationId) { | ||
| const bodyParams = params.filter((param) => param.in === "body"); | ||
| const formParams = params.filter((param) => param.in === "formData"); | ||
| if (bodyParams.length > 1) { | ||
| this.reportError(`\`${operationId}\` has ${bodyParams.length} body parameters. Only one is allowed.`); | ||
| } else if (bodyParams.length > 0 && formParams.length > 0) { | ||
| this.reportError( | ||
| `\`${operationId}\` has \`body\` and \`formData\` parameters. Only one or the other is allowed.` | ||
| ); | ||
| } | ||
| } | ||
| /** | ||
| * Validates path parameters for the given path. | ||
| * | ||
| */ | ||
| validatePathParameters(params, pathId, operationId) { | ||
| const placeholders = pathId.match(pathParameterTemplateRegExp) || []; | ||
| for (let i = 0; i < placeholders.length; i++) { | ||
| for (let j = i + 1; j < placeholders.length; j++) { | ||
| if (placeholders[i] === placeholders[j]) { | ||
| this.reportError(`\`${operationId}\` has multiple path placeholders named \`${placeholders[i]}\`.`); | ||
| } | ||
| } | ||
| } | ||
| params.filter((param) => param.in === "path").forEach((param) => { | ||
| if (param.required !== true) { | ||
| this.reportError( | ||
| `Path parameters cannot be optional. Set \`required=true\` for the \`${param.name}\` parameter at \`${operationId}\`.` | ||
| ); | ||
| } | ||
| const match = placeholders.indexOf(`{${param.name}}`); | ||
| if (match === -1) { | ||
| this.reportError( | ||
| `\`${operationId}\` has a path parameter named \`${param.name}\`, but there is no corresponding \`{${param.name}}\` in the path string.` | ||
| ); | ||
| } | ||
| placeholders.splice(match, 1); | ||
| }); | ||
| if (placeholders.length > 0) { | ||
| const list = new Intl.ListFormat("en", { style: "long", type: "conjunction" }).format( | ||
| placeholders.map((placeholder) => `\`${placeholder}\``) | ||
| ); | ||
| this.reportError(`\`${operationId}\` is missing path parameter(s) for ${list}.`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates data types of parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameterTypes(params, operation, operationId) { | ||
| params.forEach((param) => { | ||
| const parameterId = `${operationId}/parameters/${param.name}`; | ||
| let schema; | ||
| switch (param.in) { | ||
| case "body": | ||
| schema = param.schema; | ||
| break; | ||
| case "formData": | ||
| schema = param; | ||
| break; | ||
| default: | ||
| schema = param; | ||
| } | ||
| this.validateSchema(schema, parameterId); | ||
| this.validateRequiredPropertiesExist(schema, parameterId); | ||
| if (schema.type === "file") { | ||
| const formData = /multipart\/(.*\+)?form-data/; | ||
| const urlEncoded = /application\/(.*\+)?x-www-form-urlencoded/; | ||
| const consumes = operation.consumes || this.api.consumes || []; | ||
| const hasValidMimeType = consumes.some((consume) => { | ||
| return formData.test(consume) || urlEncoded.test(consume); | ||
| }); | ||
| if (!hasValidMimeType) { | ||
| this.reportError( | ||
| `\`${operationId}\` has a file parameter, so it must consume \`multipart/form-data\` or \`application/x-www-form-urlencoded\`.` | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given response object. | ||
| * | ||
| */ | ||
| validateResponse(code, response, responseId) { | ||
| if (code !== "default") { | ||
| if (typeof code === "number" && (code < 100 || code > 599) || typeof code === "string" && (Number(code) < 100 || Number(code) > 599)) { | ||
| this.reportError(`\`${responseId}\` has an invalid response code: ${code}`); | ||
| } | ||
| } | ||
| Object.keys(response.headers || {}).forEach((headerName) => { | ||
| const header = response.headers[headerName]; | ||
| const headerId = `${responseId}/headers/${headerName}`; | ||
| this.validateSchema(header, headerId); | ||
| }); | ||
| if (response.schema) { | ||
| if ("$ref" in response.schema) { | ||
| return; | ||
| } | ||
| this.validateSchema(response.schema, `${responseId}/schema`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given Swagger schema object. | ||
| * | ||
| */ | ||
| validateSchema(schema, schemaId) { | ||
| if (schema.type === "array" && !schema.items) { | ||
| this.reportError(`\`${schemaId}\` is an array, so it must include an \`items\` schema.`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates that the declared properties of the given Swagger schema object actually exist. | ||
| * | ||
| */ | ||
| validateRequiredPropertiesExist(schema, schemaId) { | ||
| function collectProperties(schemaObj, props) { | ||
| if (schemaObj.properties) { | ||
| Object.keys(schemaObj.properties).forEach((property) => { | ||
| if (schemaObj.properties.hasOwnProperty(property)) { | ||
| props[property] = schemaObj.properties[property]; | ||
| } | ||
| }); | ||
| } | ||
| if (schemaObj.allOf) { | ||
| schemaObj.allOf.forEach((parent) => { | ||
| collectProperties(parent, props); | ||
| }); | ||
| } | ||
| } | ||
| if (schema.required && Array.isArray(schema.required)) { | ||
| const props = {}; | ||
| collectProperties(schema, props); | ||
| schema.required.forEach((requiredProperty) => { | ||
| if (!props[requiredProperty]) { | ||
| this.reportError( | ||
| `Property \`${requiredProperty}\` is listed as required but does not exist in \`${schemaId}\`.` | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Checks the given parameter list for duplicates. | ||
| * | ||
| */ | ||
| checkForDuplicates(params, schemaId) { | ||
| for (let i = 0; i < params.length - 1; i++) { | ||
| const outer = params[i]; | ||
| for (let j = i + 1; j < params.length; j++) { | ||
| const inner = params[j]; | ||
| if (outer.name === inner.name && outer.in === inner.in) { | ||
| this.reportError(`Found multiple \`${outer.in}\` parameters named \`${outer.name}\` in \`${schemaId}\`.`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| // src/validators/spec.ts | ||
| function validateSpec(api, rules) { | ||
| let validator; | ||
| const specificationName = getSpecificationName(api); | ||
| if (isOpenAPI(api)) { | ||
| validator = new OpenAPISpecificationValidator(api, rules.openapi); | ||
| } else { | ||
| validator = new SwaggerSpecificationValidator(api); | ||
| } | ||
| validator.run(); | ||
| if (!validator.errors.length) { | ||
| return { | ||
| valid: true, | ||
| warnings: validator.warnings, | ||
| specification: specificationName | ||
| }; | ||
| } | ||
| return { | ||
| valid: false, | ||
| errors: validator.errors, | ||
| warnings: validator.warnings, | ||
| additionalErrors: 0, | ||
| specification: specificationName | ||
| }; | ||
| } | ||
| // src/index.ts | ||
| async function parse(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| const parser = new (0, _jsonschemarefparser2.default)(); | ||
| const schema = await parser.parse(args.path, args.schema, parserOptions); | ||
| repairSchema(schema, args.path); | ||
| return schema; | ||
| } | ||
| async function bundle(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| const parser = new (0, _jsonschemarefparser2.default)(); | ||
| await parser.bundle(args.path, args.schema, parserOptions); | ||
| repairSchema(parser.schema, args.path); | ||
| return parser.schema; | ||
| } | ||
| async function dereference(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| const parser = new (0, _jsonschemarefparser2.default)(); | ||
| await parser.dereference(args.path, args.schema, parserOptions); | ||
| repairSchema(parser.schema, args.path); | ||
| return parser.schema; | ||
| } | ||
| async function validate(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| let result; | ||
| const circular$RefOption = parserOptions.dereference.circular; | ||
| parserOptions.dereference.circular = "ignore"; | ||
| const parser = new (0, _jsonschemarefparser2.default)(); | ||
| try { | ||
| await parser.dereference(args.path, args.schema, parserOptions); | ||
| } catch (err) { | ||
| if (err instanceof _jsonschemarefparser.MissingPointerError) { | ||
| return { | ||
| valid: false, | ||
| errors: [{ message: err.message }], | ||
| warnings: [], | ||
| additionalErrors: 0, | ||
| specification: null | ||
| }; | ||
| } | ||
| throw err; | ||
| } | ||
| if (!isSwagger(parser.schema) && !isOpenAPI(parser.schema)) { | ||
| return { | ||
| valid: false, | ||
| errors: [{ message: "Supplied schema is not a valid API definition." }], | ||
| warnings: [], | ||
| additionalErrors: 0, | ||
| specification: null | ||
| }; | ||
| } | ||
| parserOptions.dereference.circular = circular$RefOption; | ||
| result = validateSchema(parser.schema, options); | ||
| if (!result.valid) { | ||
| return result; | ||
| } | ||
| if (_optionalChain([parser, 'access', _12 => _12.$refs, 'optionalAccess', _13 => _13.circular])) { | ||
| if (circular$RefOption === true) { | ||
| _jsonschemarefparser.dereferenceInternal.call(void 0, parser, parserOptions); | ||
| } else if (circular$RefOption === false) { | ||
| throw new ReferenceError( | ||
| "The API contains circular references but the validator is configured to not permit them." | ||
| ); | ||
| } | ||
| } | ||
| const rules = _optionalChain([options, 'optionalAccess', _14 => _14.validate, 'optionalAccess', _15 => _15.rules, 'optionalAccess', _16 => _16.openapi]); | ||
| result = validateSpec(parser.schema, { | ||
| openapi: { | ||
| "array-without-items": _optionalChain([rules, 'optionalAccess', _17 => _17["array-without-items"]]) || "error", | ||
| "duplicate-non-request-body-parameters": _optionalChain([rules, 'optionalAccess', _18 => _18["duplicate-non-request-body-parameters"]]) || "error", | ||
| "duplicate-operation-id": _optionalChain([rules, 'optionalAccess', _19 => _19["duplicate-operation-id"]]) || "error", | ||
| "non-optional-path-parameters": _optionalChain([rules, 'optionalAccess', _20 => _20["non-optional-path-parameters"]]) || "error", | ||
| "path-parameters-not-in-parameters": _optionalChain([rules, 'optionalAccess', _21 => _21["path-parameters-not-in-parameters"]]) || "error", | ||
| "path-parameters-not-in-path": _optionalChain([rules, 'optionalAccess', _22 => _22["path-parameters-not-in-path"]]) || "error" | ||
| } | ||
| }); | ||
| return result; | ||
| } | ||
| function compileErrors(result) { | ||
| const specName = result.specification || "API definition"; | ||
| const status = !result.valid ? "failed" : "succeeded, but with warnings"; | ||
| const message = [`${specName} schema validation ${status}.`]; | ||
| if (result.valid === false) { | ||
| if (result.errors.length) { | ||
| message.push(...result.errors.map((err) => err.message)); | ||
| } | ||
| } | ||
| if (result.warnings.length) { | ||
| if (result.valid === false && result.errors.length) { | ||
| message.push("We have also found some additional warnings:"); | ||
| } | ||
| message.push(...result.warnings.map((warn) => warn.message)); | ||
| } | ||
| if (result.valid === false && result.additionalErrors > 0) { | ||
| message.push( | ||
| `Plus an additional ${result.additionalErrors} errors. Please resolve the above and re-run validation to see more.` | ||
| ); | ||
| } | ||
| return message.join("\n\n"); | ||
| } | ||
| exports.bundle = bundle; exports.compileErrors = compileErrors; exports.dereference = dereference; exports.parse = parse; exports.validate = validate; | ||
| //# sourceMappingURL=index.cjs.map |
| {"version":3,"sources":["/Users/erunion/code/readme/oas/packages/parser/dist/index.cjs","../src/index.ts","../src/lib/index.ts","../src/util.ts","../src/repair.ts","../src/validators/schema.ts","../src/lib/reduceAjvErrors.ts","../src/validators/spec/index.ts","../src/validators/spec/openapi.ts","../src/validators/spec/swagger.ts","../src/validators/spec.ts"],"names":[],"mappings":"AAAA;ACEA,oJAAqE;ADArE;AACA;AEMO,IAAM,4BAAA,EAAsC,cAAA;AAQ5C,IAAM,qBAAA,EAAuB,CAAC,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,MAAA,EAAQ,OAAO,CAAA;AACjG,IAAM,mBAAA,EAAqB,CAAC,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,SAAA,EAAW,MAAA,EAAQ,OAAO,CAAA;AAOtF,SAAS,SAAA,CAAU,MAAA,EAA2C;AACnE,EAAA,OAAO,UAAA,GAAa,OAAA,GAAU,MAAA,CAAO,QAAA,IAAY,KAAA,CAAA;AACnD;AAOO,SAAS,SAAA,CAAU,MAAA,EAAkE;AAC1F,EAAA,OAAO,UAAA,GAAa,OAAA,GAAU,MAAA,CAAO,QAAA,IAAY,KAAA,CAAA;AACnD;AAOO,SAAS,WAAA,CAAY,MAAA,EAA2C;AACrE,EAAA,OAAO,UAAA,GAAa,OAAA,GAAU,MAAA,CAAO,QAAA,IAAY,KAAA,EAAA,GAAa,MAAA,CAAO,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA;AAC/F;AAOO,SAAS,WAAA,CAAY,MAAA,EAAoE;AAC9F,EAAA,OAAO,UAAA,GAAa,OAAA,GAAU,MAAA,CAAO,QAAA,IAAY,KAAA,EAAA,GAAa,MAAA,CAAO,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA;AAC/F;AAMO,SAAS,oBAAA,CACd,GAAA,EACuB;AACvB,EAAA,OAAO,SAAA,CAAU,GAAG,EAAA,EAAI,UAAA,EAAY,SAAA;AACtC;AF1CA;AACA;AGpBA;AHsBA;AACA;AIXA,SAAS,UAAA,CACP,MAAA,EACA,IAAA,EACA;AAEA,EAAA,GAAA,CAAI,OAAA,GAAU,MAAA,GAAS,OAAA,GAAU,MAAA,CAAO,IAAA,GAAO,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,EAAG;AACzE,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,EAAQ,IAAI,GAAA,CAAI,IAAI,CAAA;AAG1B,MAAA,MAAA,CAAO,IAAA,EAAM,CAAA,EAAA;AACP,IAAA;AAER,IAAA;AACF,EAAA;AACF;AAmBgB;AACE,EAAA;AACd,IAAA;AACF,EAAA;AAEW,EAAA;AACM,IAAA;AACjB,EAAA;AAEW,EAAA;AACL,IAAA;AACI,MAAA;AACM,MAAA;AACJ,QAAA;AACM,QAAA;AACJ,UAAA;AACD,UAAA;AACH,YAAA;AACF,UAAA;AAQI,UAAA;AACF,YAAA;AACA,YAAA;AACF,UAAA;AASE,UAAA;AAKA,YAAA;AACF,UAAA;AACD,QAAA;AACF,MAAA;AACH,IAAA;AACD,EAAA;AACH;AJjCoB;AACA;AGtDJ;AACA,EAAA;AAGZ,IAAA;AACF,EAAA;AACF;AAOgB;AAGP,EAAA;AACQ,IAAA;AACE,IAAA;AACjB,EAAA;AACF;AAMgB;AACR,EAAA;AACC,EAAA;AACF,IAAA;AACU,IAAA;AACR,MAAA;AAGD,MAAA;AAGU,MAAA;AACZ,MAAA;AAAgF;AAAA;AAAA;AAAA;AAMhF,MAAA;AACF,IAAA;AAES,IAAA;AACJ,MAAA;AAGD,MAAA;AAEI,MAAA;AACO,QAAA;AACF,QAAA;AACX,MAAA;AACF,IAAA;AACF,EAAA;AACF;AHgCoB;AACA;AKrGb;AACE;AACO;AACT;ALuGa;AACA;AMjGJ;AACI,EAAA;AAEH,EAAA;AAKR,IAAA;AACH,MAAA;AACF,IAAA;AAGe,IAAA;AACC,MAAA;AACd,MAAA;AACS,IAAA;AAGT,MAAA;AACF,IAAA;AAKI,IAAA;AACM,IAAA;AACC,MAAA;AACP,QAAA;AACF,MAAA;AACD,IAAA;AAEG,IAAA;AACY,MAAA;AAChB,IAAA;AACD,EAAA;AAGc,EAAA;AACN,IAAA;AACT,EAAA;AAEW,EAAA;AACb;ANkFoB;AACA;AKlHd;AACA;AAMG;AACM,EAAA;AACA,IAAA;AACH,IAAA;AACR,IAAA;AACF,EAAA;AAEa,EAAA;AACA,IAAA;AACb,EAAA;AAEe,EAAA;AACjB;AAMgB;AAIV,EAAA;AAGA,EAAA;AACE,EAAA;AAEW,EAAA;AACN,IAAA;AACH,IAAA;AACG,EAAA;AACA,IAAA;AASH,IAAA;AACF,IAAA;AACK,MAAA;AACT,IAAA;AAIa,IAAA;AAEA,IAAA;AAEA,IAAA;AAEA,IAAA;AAGP,IAAA;AACD,EAAA;AACI,IAAA;AACH,IAAA;AACR,EAAA;AAGgB,EAAA;AACH,EAAA;AAEK,IAAA;AAClB,EAAA;AAEI,EAAA;AACA,EAAA;AACc,EAAA;AACZ,IAAA;AACO,MAAA;AACP,QAAA;AACA,QAAA;AACF,MAAA;AAEc,IAAA;AAGhB,IAAA;AACF,EAAA;AAEI,EAAA;AAEa,IAAA;AACL,MAAA;AACE,MAAA;AACF,MAAA;AACT,IAAA;AAEM,IAAA;AACE,MAAA;AACP,MAAA;AACW,MAAA;AACX,MAAA;AACA,MAAA;AACF,IAAA;AACY,EAAA;AAGL,IAAA;AACE,MAAA;AACI,MAAA;AACA,MAAA;AACX,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AACF;ALsEoB;AACA;AOpNE;AACM,iBAAA;AAEI,kBAAA;AAER,EAAA;AACR,IAAA;AACd,EAAA;AAEwB,EAAA;AACR,IAAA;AAChB,EAAA;AAGF;APiNoB;AACA;AQhNP;AACX,EAAA;AAEA,EAAA;AAE4D,EAAA;AACpD,IAAA;AAEK,IAAA;AACE,IAAA;AACf,EAAA;AAEY,EAAA;AACJ,IAAA;AACM,IAAA;AACG,MAAA;AACP,MAAA;AAEM,MAAA;AACL,QAAA;AACP,MAAA;AACD,IAAA;AASe,IAAA;AACD,MAAA;AACC,QAAA;AACH,UAAA;AACA,YAAA;AACG,cAAA;AAED,cAAA;AACH,gBAAA;AACF,cAAA;AACF,YAAA;AACD,UAAA;AACF,QAAA;AACH,MAAA;AACF,IAAA;AASgB,IAAA;AACF,MAAA;AACL,QAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAOE,EAAA;AAIA,IAAA;AACQ,MAAA;AACA,MAAA;AAEF,MAAA;AACI,QAAA;AACF,QAAA;AACG,UAAA;AACH,YAAA;AACF,UAAA;AACO,YAAA;AACA,UAAA;AACA,YAAA;AACP,UAAA;AACF,QAAA;AAEK,QAAA;AAEO,QAAA;AACJ,UAAA;AACA,UAAA;AACF,UAAA;AACG,YAAA;AACP,UAAA;AACD,QAAA;AACH,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AAMA,IAAA;AACA,IAAA;AAGD,IAAA;AAGA,IAAA;AAIU,IAAA;AACP,MAAA;AACA,QAAA;AACK,UAAA;AACT,QAAA;AAEO,QAAA;AACR,MAAA;AAEI,MAAA;AACH,QAAA;AACF,MAAA;AAEO,MAAA;AACN,IAAA;AAEE,IAAA;AACA,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AAIA,IAAA;AAGI,IAAA;AAGI,MAAA;AACC,QAAA;AACF,UAAA;AACH,YAAA;AACF,UAAA;AACK,QAAA;AACA,UAAA;AACH,YAAA;AACF,UAAA;AACF,QAAA;AACF,MAAA;AAEc,MAAA;AACA,MAAA;AACN,QAAA;AAEG,QAAA;AACF,UAAA;AACA,QAAA;AACA,UAAA;AACP,QAAA;AACF,MAAA;AAEa,MAAA;AACd,IAAA;AAEC,IAAA;AACW,MAAA;AACX,QAAA;AACF,MAAA;AAEc,MAAA;AACL,MAAA;AACF,QAAA;AACA,MAAA;AACA,QAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACS,IAAA;AACC,MAAA;AACZ,QAAA;AACF,MAAA;AAOW,MAAA;AACT,QAAA;AACS,MAAA;AACT,QAAA;AACF,MAAA;AAEM,MAAA;AAED,MAAA;AACN,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAAA;AAMyB,EAAA;AACX,IAAA;AACJ,MAAA;AACA,MAAA;AACQ,MAAA;AACZ,QAAA;AACF,MAAA;AAEW,MAAA;AACH,QAAA;AACC,UAAA;AACP,QAAA;AACS,MAAA;AACG,QAAA;AACN,UAAA;AACI,YAAA;AACC,cAAA;AACP,YAAA;AACF,UAAA;AACD,QAAA;AACH,MAAA;AACD,IAAA;AAEY,IAAA;AACC,MAAA;AACN,QAAA;AACI,UAAA;AACC,YAAA;AACP,UAAA;AACF,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMuB,EAAA;AACV,IAAA;AACA,MAAA;AACF,QAAA;AACA,MAAA;AACA,QAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACU,IAAA;AACA,MAAA;AACD,MAAA;AACL,QAAA;AACF,QAAA;AACF,UAAA;AACF,QAAA;AAEU,QAAA;AACF,UAAA;AAEG,UAAA;AACF,YAAA;AACA,UAAA;AACA,YAAA;AACP,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACF;ARsIoB;AACA;AStbP;AACX,EAAA;AAEqC,EAAA;AAC7B,IAAA;AAEK,IAAA;AACb,EAAA;AAEY,EAAA;AACJ,IAAA;AACM,IAAA;AACG,MAAA;AACP,MAAA;AAEM,MAAA;AACL,QAAA;AACP,MAAA;AACD,IAAA;AAEW,IAAA;AACJ,MAAA;AACA,MAAA;AAED,MAAA;AACE,QAAA;AACE,UAAA;AACP,QAAA;AACF,MAAA;AAEK,MAAA;AACN,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAAA;AAMqB,EAAA;AACnB,IAAA;AACQ,MAAA;AACA,MAAA;AAEF,MAAA;AACI,QAAA;AACF,QAAA;AACG,UAAA;AACH,YAAA;AACK,UAAA;AACA,YAAA;AACP,UAAA;AACF,QAAA;AAEK,QAAA;AAEO,QAAA;AACJ,UAAA;AACF,UAAA;AACF,YAAA;AACF,UAAA;AAEM,UAAA;AACD,UAAA;AACN,QAAA;AACH,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AAMA,IAAA;AACA,IAAA;AACO,MAAA;AACb,IAAA;AAGK,IAAA;AAGA,IAAA;AAIU,IAAA;AACP,MAAA;AACA,QAAA;AACK,UAAA;AACT,QAAA;AACO,QAAA;AACR,MAAA;AACI,MAAA;AACH,QAAA;AACF,MAAA;AACO,MAAA;AACN,IAAA;AAEE,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACA,IAAA;AACA,IAAA;AAGS,IAAA;AACR,MAAA;AACI,IAAA;AAEJ,MAAA;AACE,QAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AAEA,IAAA;AAGU,IAAA;AACD,MAAA;AACP,QAAA;AACG,UAAA;AACP,QAAA;AACF,MAAA;AACF,IAAA;AAGU,IAAA;AAEI,MAAA;AACH,QAAA;AACH,UAAA;AACF,QAAA;AACF,MAAA;AAEc,MAAA;AACA,MAAA;AACP,QAAA;AACE,UAAA;AACP,QAAA;AACF,MAAA;AAEa,MAAA;AACd,IAAA;AAEC,IAAA;AACW,MAAA;AACX,QAAA;AACF,MAAA;AAEK,MAAA;AACP,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AAKS,IAAA;AACP,MAAA;AACF,MAAA;AAEU,MAAA;AACP,QAAA;AACM,UAAA;AACT,UAAA;AACG,QAAA;AACM,UAAA;AACT,UAAA;AACF,QAAA;AACW,UAAA;AACb,MAAA;AAEK,MAAA;AACA,MAAA;AAEM,MAAA;AAEH,QAAA;AACA,QAAA;AAEA,QAAA;AAEA,QAAA;AACG,UAAA;AACR,QAAA;AAEI,QAAA;AACE,UAAA;AACE,YAAA;AACP,UAAA;AACF,QAAA;AACF,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAAA;AAMyB,EAAA;AAOV,IAAA;AAED,MAAA;AAGH,QAAA;AACP,MAAA;AACF,IAAA;AAEY,IAAA;AACJ,MAAA;AACA,MAAA;AACD,MAAA;AACN,IAAA;AAEY,IAAA;AACG,MAAA;AACZ,QAAA;AACF,MAAA;AAEK,MAAA;AACP,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMuB,EAAA;AACV,IAAA;AACJ,MAAA;AACP,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AAEG,IAAA;AACO,MAAA;AACA,QAAA;AAEN,UAAA;AAEI,YAAA;AACR,UAAA;AACD,QAAA;AACH,MAAA;AAEc,MAAA;AACF,QAAA;AACR,UAAA;AACD,QAAA;AACH,MAAA;AACF,IAAA;AAEW,IAAA;AACkC,MAAA;AAC3C,MAAA;AACO,MAAA;AACM,QAAA;AACJ,UAAA;AACH,YAAA;AACF,UAAA;AACF,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACU,IAAA;AACA,MAAA;AACD,MAAA;AACL,QAAA;AACI,QAAA;AACH,UAAA;AACP,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACF;AT8WoB;AACA;AU1qBJ;AAMV,EAAA;AAEE,EAAA;AACW,EAAA;AACC,IAAA;AACX,EAAA;AACW,IAAA;AAClB,EAAA;AAEc,EAAA;AAEC,EAAA;AACN,IAAA;AACE,MAAA;AACG,MAAA;AACV,MAAA;AACF,IAAA;AACF,EAAA;AAEO,EAAA;AACE,IAAA;AACC,IAAA;AACE,IAAA;AACV,IAAA;AACe,IAAA;AACjB,EAAA;AACF;AVmqBoB;AACA;AC/rBE;AACP,EAAA;AACP,EAAA;AAES,EAAA;AACA,EAAA;AAGF,EAAA;AAEN,EAAA;AACT;AAYsB;AAIP,EAAA;AACP,EAAA;AAES,EAAA;AACF,EAAA;AAGA,EAAA;AAEC,EAAA;AAChB;AAYsB;AAIP,EAAA;AACP,EAAA;AAES,EAAA;AACF,EAAA;AAGA,EAAA;AAEC,EAAA;AAChB;AAsBsB;AAIP,EAAA;AACP,EAAA;AAEF,EAAA;AAIE,EAAA;AACQ,EAAA;AAEC,EAAA;AACX,EAAA;AACW,IAAA;AACD,EAAA;AAIR,IAAA;AACK,MAAA;AACE,QAAA;AACI,QAAA;AACA,QAAA;AACX,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACR,EAAA;AAEe,EAAA;AACN,IAAA;AACE,MAAA;AACI,MAAA;AACA,MAAA;AACX,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAGc,EAAA;AAIL,EAAA;AACG,EAAA;AACH,IAAA;AACT,EAAA;AAEkB,EAAA;AACZ,IAAA;AAEF,MAAA;AACS,IAAA;AAEC,MAAA;AACR,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAGc,EAAA;AACL,EAAA;AACE,IAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACD,EAAA;AAEM,EAAA;AACT;AAOgB;AACG,EAAA;AACD,EAAA;AAEW,EAAA;AAEhB,EAAA;AACE,IAAA;AACI,MAAA;AACf,IAAA;AACF,EAAA;AAEW,EAAA;AACE,IAAA;AACI,MAAA;AACf,IAAA;AAEgB,IAAA;AAClB,EAAA;AAEW,EAAA;AACD,IAAA;AACN,MAAA;AACF,IAAA;AACF,EAAA;AAEe,EAAA;AACjB;ADgmBoB;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/erunion/code/readme/oas/packages/parser/dist/index.cjs","sourcesContent":[null,"import type { APIDocument, ParserOptions, ValidationResult, ErrorDetails, WarningDetails } from './types.js';\n\nimport $RefParser, { dereferenceInternal, MissingPointerError } from '@apidevtools/json-schema-ref-parser';\n\nimport { isSwagger, isOpenAPI } from './lib/index.js';\nimport { convertOptionsForParser, normalizeArguments, repairSchema } from './util.js';\nimport { validateSchema } from './validators/schema.js';\nimport { validateSpec } from './validators/spec.js';\n\nexport type { ParserOptions, ValidationResult, ErrorDetails, WarningDetails };\n\n/**\n * Parses the given API definition, in JSON or YAML format, and returns it as a JSON object. This\n * method **does not** resolve `$ref` pointers or dereference anything. It simply parses _one_ file\n * and returns it.\n *\n * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself.\n * @param options\n */\nexport async function parse<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S> {\n const args = normalizeArguments<S>(api);\n const parserOptions = convertOptionsForParser(options);\n\n const parser = new $RefParser<S>();\n const schema = await parser.parse(args.path, args.schema, parserOptions);\n\n // If necessary, repair the schema of any anomalies and quirks.\n repairSchema(schema, args.path);\n\n return schema;\n}\n\n/**\n * Bundles all referenced files and URLs into a single API definition that only has _internal_\n * `$ref` pointers. This lets you split up your definition however you want while you're building\n * it, but later combine all those files together when it's time to package or distribute the API\n * definition to other people. The resulting definition size will be small, since it will still\n * contain _internal_ JSON references rather than being fully-dereferenced.\n *\n * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself.\n * @param options\n */\nexport async function bundle<S extends APIDocument = APIDocument>(\n api: S | string,\n options?: ParserOptions,\n): Promise<S> {\n const args = normalizeArguments<S>(api);\n const parserOptions = convertOptionsForParser(options);\n\n const parser = new $RefParser<S>();\n await parser.bundle(args.path, args.schema, parserOptions);\n\n // If necessary, repair the schema of any anomalies and quirks.\n repairSchema(parser.schema, args.path);\n\n return parser.schema;\n}\n\n/**\n * Dereferences all `$ref` pointers in the supplied API definition, replacing each reference with\n * its resolved value. This results in an API definition that does not contain _any_ `$ref`\n * pointers. Instead, it's a normal JSON object tree that can easily be crawled and used just like\n * any other object. This is great for programmatic usage, especially when using tools that don't\n * understand JSON references.\n *\n * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself.\n * @param options\n */\nexport async function dereference<S extends APIDocument = APIDocument>(\n api: S | string,\n options?: ParserOptions,\n): Promise<S> {\n const args = normalizeArguments<S>(api);\n const parserOptions = convertOptionsForParser(options);\n\n const parser = new $RefParser<S>();\n await parser.dereference(args.path, args.schema, parserOptions);\n\n // If necessary, repair the schema of any anomalies and quirks.\n repairSchema(parser.schema, args.path);\n\n return parser.schema;\n}\n\n/**\n * Validates the API definition against the Swagger 2.0, OpenAPI 3.0, or OpenAPI 3.1 specifications.\n *\n * In addition to validating the API definition against their respective specification schemas it\n * will also be validated against specific areas that aren't covered by the Swagger or OpenAPI\n * schemas, such as duplicate parameters, invalid component schema names, or duplicate\n * `operationId` values.\n *\n * If validation fails an error will be thrown with information about what, and where, the error\n * lies within the API definition.\n *\n * Internally this method invokes [`dereference()`](#dereference) so the returned object, whether\n * its a Swagger or OpenAPI definition, will be fully dereferenced.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v2.0}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.0}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.1}\n * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself.\n * @param options\n */\nexport async function validate<S extends APIDocument, Options extends ParserOptions>(\n api: S | string,\n options?: Options,\n): Promise<ValidationResult> {\n const args = normalizeArguments<S>(api);\n const parserOptions = convertOptionsForParser(options);\n\n let result: ValidationResult;\n\n // ZSchema doesn't support circular objects, so don't dereference circular $refs yet\n // (see https://github.com/zaggino/z-schema/issues/137)\n const circular$RefOption = parserOptions.dereference.circular;\n parserOptions.dereference.circular = 'ignore';\n\n const parser = new $RefParser<S>();\n try {\n await parser.dereference(args.path, args.schema, parserOptions);\n } catch (err) {\n // `json-schema-ref-parser` will throw exceptions on things like `$ref` pointers that can't\n // be resolved so we need to capture and reformat those into our expected `ValidationResult`\n // format.\n if (err instanceof MissingPointerError) {\n return {\n valid: false,\n errors: [{ message: err.message }],\n warnings: [],\n additionalErrors: 0,\n specification: null,\n };\n }\n\n throw err;\n }\n\n if (!isSwagger(parser.schema) && !isOpenAPI(parser.schema)) {\n return {\n valid: false,\n errors: [{ message: 'Supplied schema is not a valid API definition.' }],\n warnings: [],\n additionalErrors: 0,\n specification: null,\n };\n }\n\n // Restore the original options, now that we're done dereferencing\n parserOptions.dereference.circular = circular$RefOption;\n\n // Validate the API against the OpenAPI or Swagger JSON schema definition.\n // NOTE: This is safe to do, because we haven't dereferenced circular $refs yet\n result = validateSchema(parser.schema, options);\n if (!result.valid) {\n return result;\n }\n\n if (parser.$refs?.circular) {\n if (circular$RefOption === true) {\n // The API has circular reference so we need to do a second pass to fully dereference it.\n dereferenceInternal<S>(parser, parserOptions);\n } else if (circular$RefOption === false) {\n // The API has circular references but we're configured to not permit that.\n throw new ReferenceError(\n 'The API contains circular references but the validator is configured to not permit them.',\n );\n }\n }\n\n // Validate the API against the OpenAPI or Swagger specification.\n const rules = options?.validate?.rules?.openapi;\n result = validateSpec(parser.schema, {\n openapi: {\n 'array-without-items': rules?.['array-without-items'] || 'error',\n 'duplicate-non-request-body-parameters': rules?.['duplicate-non-request-body-parameters'] || 'error',\n 'duplicate-operation-id': rules?.['duplicate-operation-id'] || 'error',\n 'non-optional-path-parameters': rules?.['non-optional-path-parameters'] || 'error',\n 'path-parameters-not-in-parameters': rules?.['path-parameters-not-in-parameters'] || 'error',\n 'path-parameters-not-in-path': rules?.['path-parameters-not-in-path'] || 'error',\n },\n });\n\n return result;\n}\n\n/**\n * A utility to transform the `ValidationResult` from a `validate()` call into a human-readable\n * string.\n *\n */\nexport function compileErrors(result: ValidationResult): string {\n const specName = result.specification || 'API definition';\n const status = !result.valid ? 'failed' : 'succeeded, but with warnings';\n\n const message: string[] = [`${specName} schema validation ${status}.`];\n\n if (result.valid === false) {\n if (result.errors.length) {\n message.push(...result.errors.map(err => err.message));\n }\n }\n\n if (result.warnings.length) {\n if (result.valid === false && result.errors.length) {\n message.push('We have also found some additional warnings:');\n }\n\n message.push(...result.warnings.map(warn => warn.message));\n }\n\n if (result.valid === false && result.additionalErrors > 0) {\n message.push(\n `Plus an additional ${result.additionalErrors} errors. Please resolve the above and re-run validation to see more.`,\n );\n }\n\n return message.join('\\n\\n');\n}\n","import type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\n/**\n * Regular expression that matches path parameter templating.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-templating}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#path-templating}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-templating}\n */\nexport const pathParameterTemplateRegExp: RegExp = /\\{([^/}]+)}/g;\n\n/**\n * List of HTTP verbs used for OperationItem as per the OpenAPI and Swagger specifications\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-item-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#path-item-object}\n */\nexport const supportedHTTPMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'] as const;\nexport const swaggerHTTPMethods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch'] as const;\n\n/**\n * Is a given object a Swagger API definition?\n *\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isSwagger(schema: any): schema is OpenAPIV2.Document {\n return 'swagger' in schema && schema.swagger !== undefined;\n}\n\n/**\n * Is a given object an OpenAPI API definition?\n *\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isOpenAPI(schema: any): schema is OpenAPIV3_1.Document | OpenAPIV3.Document {\n return 'openapi' in schema && schema.openapi !== undefined;\n}\n\n/**\n * Is a given object an OpenAPI 3.0 API definition?\n *\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isOpenAPI30(schema: any): schema is OpenAPIV3.Document {\n return 'openapi' in schema && schema.openapi !== undefined && schema.openapi.startsWith('3.0');\n}\n\n/**\n * Is a given object an OpenAPI 3.1 API definition?\n *\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function isOpenAPI31(schema: any): schema is OpenAPIV3_1.Document | OpenAPIV3_1.Document {\n return 'openapi' in schema && schema.openapi !== undefined && schema.openapi.startsWith('3.1');\n}\n\n/**\n * Determine the proper name for the API specification schema used by a given schema.\n *\n */\nexport function getSpecificationName(\n api: OpenAPIV2.Document | OpenAPIV3_1.Document | OpenAPIV3.Document,\n): 'OpenAPI' | 'Swagger' {\n return isSwagger(api) ? 'Swagger' : 'OpenAPI';\n}\n","import type { APIDocument, ParserOptions } from './types.js';\nimport type { ParserOptions as $RefParserOptions } from '@apidevtools/json-schema-ref-parser';\n\nimport { getJsonSchemaRefParserDefaultOptions } from '@apidevtools/json-schema-ref-parser';\n\nimport { isOpenAPI } from './lib/index.js';\nimport { fixOasRelativeServers } from './repair.js';\n\n/**\n * If necessary, repair the schema of any anomalies and quirks.\n *\n */\nexport function repairSchema<S extends APIDocument = APIDocument>(schema: S, filePath?: string): void {\n if (isOpenAPI(schema)) {\n // This is an OpenAPI v3 schema, check if the configured `servers` have any relative paths and\n // fix them if the content was pulled from a web resource.\n fixOasRelativeServers(schema, filePath);\n }\n}\n\n/**\n * Normalize our library variable arguments into a standard format to be used within\n * `json-schema-ref-parser`.\n *\n */\nexport function normalizeArguments<S extends APIDocument = APIDocument>(\n api: S | string,\n): { path: string; schema: S | undefined } {\n return {\n path: typeof api === 'string' ? api : '',\n schema: typeof api === 'object' ? (api as S) : undefined,\n };\n}\n\n/**\n * Convert our option set to be used within `json-schema-ref-parser`.\n *\n */\nexport function convertOptionsForParser(options: ParserOptions): Partial<$RefParserOptions> {\n const parserOptions = getJsonSchemaRefParserDefaultOptions();\n return {\n ...parserOptions,\n dereference: {\n ...parserOptions.dereference,\n\n circular:\n options?.dereference && 'circular' in options.dereference\n ? options.dereference.circular\n : parserOptions.dereference.circular,\n onCircular: options?.dereference?.onCircular || parserOptions.dereference.onCircular,\n onDereference: options?.dereference?.onDereference || parserOptions.dereference.onDereference,\n\n // OpenAPI 3.1 allows for `summary` and `description` properties at the same level as a `$ref`\n // pointer to be preserved when that `$ref` pointer is dereferenced. The default behavior of\n // `json-schema-ref-parser` is to discard these properties but this option allows us to\n // override that behavior.\n preservedProperties: ['summary', 'description'],\n },\n\n resolve: {\n ...parserOptions.resolve,\n\n external:\n options?.resolve && 'external' in options.resolve ? options.resolve.external : parserOptions.resolve.external,\n\n http: {\n ...(typeof parserOptions.resolve.http === 'object' ? parserOptions.resolve.http : {}),\n timeout: options?.resolve?.http && 'timeout' in options.resolve.http ? options.resolve.http.timeout : 5000,\n },\n },\n };\n}\n","import type { OpenAPI, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\nimport { isOpenAPI, supportedHTTPMethods } from './lib/index.js';\n\n/**\n * This function takes in a `ServerObject`, checks if it has relative path and then fixes it as per\n * the path URL.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#server-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#server-object}\n *\n * @param server - The server object to be fixed.\n * @param path - The path (an HTTP(S) url) from where the file was downloaded.\n * @returns The fixed server object\n */\nfunction fixServers(\n server: OpenAPIV3_1.ReferenceObject | OpenAPIV3.ParameterObject | OpenAPIV3.ServerObject,\n path: string,\n) {\n // A server URL starting with \"/\" tells that it is not an HTTP(s) URL.\n if (server && 'url' in server && server.url && server.url.startsWith('/')) {\n try {\n const inUrl = new URL(path);\n\n // eslint-disable-next-line no-param-reassign\n server.url = `${inUrl.protocol}//${inUrl.hostname}${server.url}`;\n } catch {\n // The server path isn't valid but we shouldn't crash out.\n }\n }\n}\n\n/**\n * This function helps fix the relative servers in the API definition file be at root, path or\n * operation's level.\n *\n * From the OpenAPI v3 specification for the `ServerObject` `url` property:\n *\n * REQUIRED. A URL to the target host. This URL supports Server Variables and MAY be relative,\n * to indicate that the host location is relative to the location where the OpenAPI document is\n * being served. Variable substitutions will be made when a variable is named in `{brackets}`.\n *\n * Further the spec says that `servers` property can show up at root level, in `PathItemObject` or\n * in `OperationObject`. However interpretation of the spec says that relative paths for servers\n * should take into account the hostname that serves the OpenAPI file.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#server-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#server-object}\n */\nexport function fixOasRelativeServers(schema: OpenAPI.Document, filePath?: string): void {\n if (!schema || !isOpenAPI(schema) || !filePath || (!filePath.startsWith('http:') && !filePath.startsWith('https:'))) {\n return;\n }\n\n if (schema.servers) {\n schema.servers.map(server => fixServers(server, filePath)); // Root level servers array's fixup\n }\n\n (['paths', 'webhooks'] as const).forEach(component => {\n if (component in schema) {\n const schemaElement = schema.paths || {};\n Object.keys(schemaElement).forEach(path => {\n const pathItem = schemaElement[path] || {};\n Object.keys(pathItem).forEach((opItem: keyof typeof pathItem) => {\n const pathItemElement = pathItem[opItem];\n if (!pathItemElement) {\n return;\n }\n\n /**\n * Servers are at the `PathItemObject` level.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#path-item-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-item-object}\n */\n if (opItem === 'servers' && Array.isArray(pathItemElement)) {\n pathItemElement.forEach(server => fixServers(server, filePath));\n return;\n }\n\n /**\n * Servers are at the `OperationObject` level.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#operation-object}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#operation-object}\n */\n if (\n supportedHTTPMethods.includes(opItem as unknown as (typeof supportedHTTPMethods)[number]) &&\n typeof pathItemElement === 'object' &&\n 'servers' in pathItemElement &&\n Array.isArray(pathItemElement.servers)\n ) {\n pathItemElement.servers.forEach(server => fixServers(server, filePath));\n }\n });\n });\n }\n });\n}\n","import type { ParserOptions, ValidationResult } from '../types.js';\nimport type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\nimport betterAjvErrors from '@readme/better-ajv-errors';\nimport { openapi } from '@readme/openapi-schemas';\nimport Ajv from 'ajv/dist/2020.js';\nimport AjvDraft4 from 'ajv-draft-04';\n\nimport { getSpecificationName, isOpenAPI31, isSwagger } from '../lib/index.js';\nimport { reduceAjvErrors } from '../lib/reduceAjvErrors.js';\n\n/**\n * We've had issues with specs larger than 2MB+ with 1,000+ errors causing memory leaks so if we\n * have a spec with more than `LARGE_SPEC_ERROR_CAP` errors and it's **stringified** length is\n * larger than `LARGE_SPEC_LIMITS` then we will only return the first `LARGE_SPEC_ERROR_CAP` errors.\n *\n * Ideally we'd be looking at the byte size of the spec instead of looking at its stringified\n * length value but the Blob API, which we'd use to get its size with `new Blob([str]).size;`, was\n * only recently introduced in Node 15.\n *\n * w/r/t the 5,000,000 limit here: The spec we found causing these memory leaks had a size of\n * 13,934,323 so 5mil seems like a decent cap to start with.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob}\n */\nconst LARGE_SPEC_ERROR_CAP = 20;\nconst LARGE_SPEC_SIZE_CAP = 5000000;\n\n/**\n * Determines which version of Ajv to load and prepares it for use.\n *\n */\nfunction initializeAjv(draft04: boolean = true) {\n const opts = {\n allErrors: true,\n strict: false,\n validateFormats: false,\n };\n\n if (draft04) {\n return new AjvDraft4(opts);\n }\n\n return new Ajv(opts);\n}\n\n/**\n * Validates the given Swagger API against the Swagger 2.0 or OpenAPI 3.0 and 3.1 schemas.\n *\n */\nexport function validateSchema(\n api: OpenAPIV2.Document | OpenAPIV3_1.Document | OpenAPIV3.Document,\n options: ParserOptions = {},\n): ValidationResult {\n let ajv;\n\n // Choose the appropriate schema (Swagger or OpenAPI)\n let schema;\n const specificationName = getSpecificationName(api);\n\n if (isSwagger(api)) {\n schema = openapi.v2;\n ajv = initializeAjv();\n } else if (isOpenAPI31(api)) {\n schema = openapi.v31legacy;\n\n /**\n * There's a bug with Ajv in how it handles `$dynamicRef` in the way that it's used within the\n * 3.1 schema so we need to do some adhoc workarounds.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/issues/2689}\n * @see {@link https://github.com/ajv-validator/ajv/issues/1573}\n */\n const schemaDynamicRef = schema.$defs.schema;\n if ('$dynamicAnchor' in schemaDynamicRef) {\n delete schemaDynamicRef.$dynamicAnchor;\n }\n\n /* eslint-disable @typescript-eslint/ban-ts-comment */\n // @ts-expect-error Intentionally setting up this funky schema for an AJV bug.\n schema.$defs.components.properties.schemas.additionalProperties = schemaDynamicRef;\n // @ts-expect-error\n schema.$defs.header.dependentSchemas.schema.properties.schema = schemaDynamicRef;\n // @ts-expect-error\n schema.$defs['media-type'].properties.schema = schemaDynamicRef;\n // @ts-expect-error\n schema.$defs.parameter.properties.schema = schemaDynamicRef;\n /* eslint-enable @typescript-eslint/ban-ts-comment */\n\n ajv = initializeAjv(false);\n } else {\n schema = openapi.v3;\n ajv = initializeAjv();\n }\n\n // Validate against the schema\n const isValid = ajv.validate(schema, api);\n if (isValid) {\n // We don't support warnings in our schema validation, only the **spec** validator.\n return { valid: true, warnings: [], specification: specificationName };\n }\n\n let additionalErrors = 0;\n let reducedErrors = reduceAjvErrors(ajv.errors);\n if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) {\n try {\n if (JSON.stringify(api).length >= LARGE_SPEC_SIZE_CAP) {\n additionalErrors = reducedErrors.length - 20;\n reducedErrors = reducedErrors.slice(0, 20);\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (error) {\n // If we failed to stringify the API definition to look at its size then we should process\n // all of its errors as-is.\n }\n }\n\n try {\n // @ts-expect-error typing on the `ErrorObject` that we use here doesn't match what `better-ajv-errors` uses\n const errors = betterAjvErrors(schema, api, reducedErrors, {\n format: 'cli-array',\n colorize: options?.validate?.errors?.colorize || false,\n indent: 2,\n });\n\n return {\n valid: false,\n errors,\n warnings: [],\n additionalErrors,\n specification: specificationName,\n };\n } catch (err) {\n // If `better-ajv-errors` fails for whatever reason we should capture and return it. We'll\n // obviously not show the user all of their validation errors but it's better than nothing.\n return {\n valid: false,\n errors: [{ message: err.message }],\n warnings: [],\n additionalErrors,\n specification: specificationName,\n };\n }\n}\n","import type { ErrorObject } from 'ajv';\n\n/**\n * Because of the way that Ajv works, if a validation error occurs deep within a schema there's a\n * chance that errors will also be thrown for its immediate parents, leading to a case where we'll\n * eventually show the error indecipherable errors like \"$ref is missing here!\" instance of what's\n * _actually_ going on where they may have mistyped `enum` as `enumm`.\n *\n * To alleviate this confusing noise, we're compressing Ajv errors down to only surface the deepest\n * point for each lineage, so that if a user typos `enum` as `enumm` we'll surface just that error\n * for them (because really that's **the** error).\n *\n */\nexport function reduceAjvErrors(errors: ErrorObject[]): ErrorObject[] {\n const flattened = new Map<string, ErrorObject>();\n\n errors.forEach(err => {\n // These two errors appear when a child schema of them has a problem and instead of polluting\n // the user with indecipherable noise we should instead relay the more specific error to them.\n // If this is all that's present in the stack then as a safety net before we wrap up we'll just\n // return the original `errors` stack.\n if ([\"must have required property '$ref'\", 'must match exactly one schema in oneOf'].includes(err.message)) {\n return;\n }\n\n // If this is our first run through let's initialize our dataset and move along.\n if (!flattened.size) {\n flattened.set(err.instancePath, err);\n return;\n } else if (flattened.has(err.instancePath)) {\n // If we already have an error recorded for this `instancePath` we can ignore it because we\n // (likely) already have recorded the more specific error.\n return;\n }\n\n // If this error hasn't already been recorded, maybe it's an error against the same\n // `instancePath` stack, in which case we should ignore it because the more specific error has\n // already been recorded.\n let shouldRecordError = true;\n flattened.forEach(flat => {\n if (flat.instancePath.includes(err.instancePath)) {\n shouldRecordError = false;\n }\n });\n\n if (shouldRecordError) {\n flattened.set(err.instancePath, err);\n }\n });\n\n // If we weren't able to fold errors down for whatever reason just return the original stack.\n if (!flattened.size) {\n return errors;\n }\n\n return [...flattened.values()];\n}\n","import type { ErrorDetails, WarningDetails } from '../../types.js';\n\nexport abstract class SpecificationValidator {\n errors: ErrorDetails[] = [];\n\n warnings: WarningDetails[] = [];\n\n protected reportError(message: string): void {\n this.errors.push({ message });\n }\n\n protected reportWarning(message: string): void {\n this.warnings.push({ message });\n }\n\n abstract run(): void;\n}\n","import type { ParserRulesOpenAPI } from '../../types.js';\nimport type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\nimport { supportedHTTPMethods, pathParameterTemplateRegExp, isOpenAPI31, isOpenAPI30 } from '../../lib/index.js';\n\nimport { SpecificationValidator } from './index.js';\n\ntype ParameterObject =\n | (OpenAPIV3_1.ParameterObject | OpenAPIV3_1.ReferenceObject)\n | (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject);\n\n/**\n * Validates parts of the OpenAPI 3.0 and 3.1 specification that aren't covered by their JSON\n * Schema definitions.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md}\n */\nexport class OpenAPISpecificationValidator extends SpecificationValidator {\n api: OpenAPIV3_1.Document | OpenAPIV3.Document;\n\n rules: ParserRulesOpenAPI;\n\n constructor(api: OpenAPIV3_1.Document | OpenAPIV3.Document, rules: ParserRulesOpenAPI) {\n super();\n\n this.api = api;\n this.rules = rules;\n }\n\n run(): void {\n const operationIds: string[] = [];\n Object.keys(this.api.paths || {}).forEach(pathName => {\n const path = this.api.paths[pathName];\n const pathId = `/paths${pathName}`;\n\n if (path && pathName.startsWith('/')) {\n this.validatePath(path, pathId, operationIds);\n }\n });\n\n /**\n * There's a problem with how the 3.0 schema uses `patternProperties` for defining the format of\n * scheme names that it ignores anything that doesn't match, so if you for example have a space\n * in a schema name it'll be seen as valid when it should instead trigger a validation error.\n *\n * @see {@link https://github.com/APIDevTools/swagger-parser/issues/184}\n */\n if (isOpenAPI30(this.api)) {\n if (this.api.components) {\n Object.keys(this.api.components).forEach((componentType: keyof typeof this.api.components) => {\n Object.keys(this.api.components[componentType]).forEach(componentName => {\n if (!/^[a-zA-Z0-9.\\-_]+$/.test(componentName)) {\n const componentId = `/components/${componentType}/${componentName}`;\n\n this.reportError(\n `\\`${componentId}\\` has an invalid name. Component names should match against: /^[a-zA-Z0-9.-_]+$/`,\n );\n }\n });\n });\n }\n }\n\n /**\n * OpenAPI 3.1 brought the addition of `webhooks` and made `paths` optional however the\n * specification requires that one or the other be present, and not be empty. Unfortunately the\n * JSON Schema for the specification is unable to specify this.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-document}\n */\n if (isOpenAPI31(this.api)) {\n if (!Object.keys(this.api.paths || {}).length && !Object.keys(this.api.webhooks || {}).length) {\n this.reportError('OpenAPI 3.1 definitions must contain at least one entry in either `paths` or `webhook`.');\n }\n }\n }\n\n /**\n * Validates the given path.\n *\n */\n private validatePath(\n path: OpenAPIV3_1.PathItemObject | OpenAPIV3.PathItemObject,\n pathId: string,\n operationIds: string[],\n ) {\n supportedHTTPMethods.forEach(operationName => {\n const operation = path[operationName];\n const operationId = `${pathId}/${operationName}`;\n\n if (operation) {\n const declaredOperationId = operation.operationId;\n if (declaredOperationId) {\n if (!operationIds.includes(declaredOperationId)) {\n operationIds.push(declaredOperationId);\n } else if (this.rules['duplicate-operation-id'] === 'warning') {\n this.reportWarning(`The operationId \\`${declaredOperationId}\\` is duplicated and should be made unique.`);\n } else {\n this.reportError(`The operationId \\`${declaredOperationId}\\` is duplicated and must be made unique.`);\n }\n }\n\n this.validateParameters(path, pathId, operation, operationId);\n\n Object.keys(operation.responses || {}).forEach(responseCode => {\n const response = operation.responses[responseCode];\n const responseId = `${operationId}/responses/${responseCode}`;\n if (response && !('$ref' in response)) {\n this.validateResponse(response, responseId);\n }\n });\n }\n });\n }\n\n /**\n * Validates the parameters for the given operation.\n *\n */\n private validateParameters(\n path: OpenAPIV3_1.PathItemObject | OpenAPIV3.PathItemObject,\n pathId: string,\n operation: OpenAPIV3_1.OperationObject | OpenAPIV3.OperationObject,\n operationId: string,\n ) {\n const pathParams = path.parameters || [];\n const operationParams = operation.parameters || [];\n\n // Check for duplicate path parameters.\n this.checkForDuplicates(pathParams, pathId);\n\n // Check for duplicate operation parameters.\n this.checkForDuplicates(operationParams, operationId);\n\n // Combine the path and operation parameters, with the operation params taking precedence over\n // the path params.\n const params = pathParams.reduce((combinedParams, value) => {\n const duplicate = combinedParams.some(param => {\n if ('$ref' in param || '$ref' in value) {\n return false;\n }\n\n return param.in === value.in && param.name === value.name;\n });\n\n if (!duplicate) {\n combinedParams.push(value);\n }\n\n return combinedParams;\n }, operationParams.slice());\n\n this.validatePathParameters(params, pathId, operationId);\n this.validateParameterTypes(params, operationId);\n }\n\n /**\n * Validates path parameters for the given path.\n *\n */\n private validatePathParameters(params: ParameterObject[], pathId: string, operationId: string) {\n // Find all `{placeholders}` in the path string. And because paths can have path parameters duped\n // we need to convert this to a unique array so we can eliminate false positives of placeholders\n // that might be duplicated.\n const placeholders = [...new Set(pathId.match(pathParameterTemplateRegExp) || [])];\n\n params\n .filter(param => 'in' in param)\n .filter(param => param.in === 'path')\n .forEach(param => {\n if (param.required !== true) {\n if (this.rules['non-optional-path-parameters'] === 'warning') {\n this.reportWarning(\n `Path parameters should not be optional. Set \\`required=true\\` for the \\`${param.name}\\` parameter at \\`${operationId}\\`.`,\n );\n } else {\n this.reportError(\n `Path parameters cannot be optional. Set \\`required=true\\` for the \\`${param.name}\\` parameter at \\`${operationId}\\`.`,\n );\n }\n }\n\n const match = placeholders.indexOf(`{${param.name}}`);\n if (match === -1) {\n const error = `\\`${operationId}\\` has a path parameter named \\`${param.name}\\`, but there is no corresponding \\`{${param.name}}\\` in the path string.`;\n\n if (this.rules['path-parameters-not-in-path'] === 'warning') {\n this.reportWarning(error);\n } else {\n this.reportError(error);\n }\n }\n\n placeholders.splice(match, 1);\n });\n\n if (placeholders.length > 0) {\n const list = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }).format(\n placeholders.map(placeholder => `\\`${placeholder}\\``),\n );\n\n const error = `\\`${operationId}\\` is missing path parameter(s) for ${list}.`;\n if (this.rules['path-parameters-not-in-parameters'] === 'warning') {\n this.reportWarning(error);\n } else {\n this.reportError(error);\n }\n }\n }\n\n /**\n * Validates data types of parameters for the given operation.\n *\n */\n private validateParameterTypes(params: ParameterObject[], operationId: string) {\n params.forEach(param => {\n if ('$ref' in param) {\n return;\n }\n\n /**\n * @todo add better handling when `content` is present instead of `schema`.\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#fixed-fields-10}\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-10}\n */\n if (!param.schema && param.content) {\n return;\n } else if ('$ref' in param.schema) {\n return;\n }\n\n const parameterId = `${operationId}/parameters/${param.name}`;\n\n this.validateSchema(param.schema, parameterId);\n });\n }\n\n /**\n * Validates the given response object.\n *\n */\n private validateResponse(response: OpenAPIV3_1.ResponseObject | OpenAPIV3.ResponseObject, responseId: string) {\n Object.keys(response.headers || {}).forEach(headerName => {\n const header = response.headers[headerName];\n const headerId = `${responseId}/headers/${headerName}`;\n if ('$ref' in header) {\n return;\n }\n\n if (header.schema) {\n if (!('$ref' in header.schema)) {\n this.validateSchema(header.schema, headerId);\n }\n } else if (header.content) {\n Object.keys(header.content).forEach(mediaType => {\n if (header.content[mediaType].schema) {\n if (!('$ref' in header.content[mediaType].schema)) {\n this.validateSchema(header.content[mediaType].schema || {}, `${headerId}/content/${mediaType}/schema`);\n }\n }\n });\n }\n });\n\n if (response.content) {\n Object.keys(response.content).forEach(mediaType => {\n if (response.content[mediaType].schema) {\n if (!('$ref' in response.content[mediaType].schema)) {\n this.validateSchema(response.content[mediaType].schema || {}, `${responseId}/content/${mediaType}/schema`);\n }\n }\n });\n }\n }\n\n /**\n * Validates the given Swagger schema object.\n *\n */\n private validateSchema(schema: OpenAPIV3_1.SchemaObject | OpenAPIV3.SchemaObject, schemaId: string) {\n if (schema.type === 'array' && !schema.items) {\n if (this.rules['array-without-items'] === 'warning') {\n this.reportWarning(`\\`${schemaId}\\` is an array, so it should include an \\`items\\` schema.`);\n } else {\n this.reportError(`\\`${schemaId}\\` is an array, so it must include an \\`items\\` schema.`);\n }\n }\n }\n\n /**\n * Checks the given parameter list for duplicates.\n *\n */\n private checkForDuplicates(params: ParameterObject[], schemaId: string) {\n for (let i = 0; i < params.length - 1; i++) {\n const outer = params[i];\n for (let j = i + 1; j < params.length; j++) {\n const inner = params[j];\n if ('$ref' in outer || '$ref' in inner) {\n continue;\n }\n\n if (outer.name === inner.name && outer.in === inner.in) {\n const error = `Found multiple \\`${outer.in}\\` parameters named \\`${outer.name}\\` in \\`${schemaId}\\`.`;\n\n if (this.rules['duplicate-non-request-body-parameters'] === 'warning') {\n this.reportWarning(error);\n } else {\n this.reportError(error);\n }\n }\n }\n }\n }\n}\n","import type { IJsonSchema, OpenAPIV2 } from 'openapi-types';\n\nimport { swaggerHTTPMethods, pathParameterTemplateRegExp } from '../../lib/index.js';\n\nimport { SpecificationValidator } from './index.js';\n\n/**\n * Validates parts of the Swagger 2.0 specification that aren't covered by its JSON Schema\n * definitions.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md}\n */\nexport class SwaggerSpecificationValidator extends SpecificationValidator {\n api: OpenAPIV2.Document;\n\n constructor(api: OpenAPIV2.Document) {\n super();\n\n this.api = api;\n }\n\n run(): void {\n const operationIds: string[] = [];\n Object.keys(this.api.paths || {}).forEach(pathName => {\n const path = this.api.paths[pathName];\n const pathId = `/paths${pathName}`;\n\n if (path && pathName.startsWith('/')) {\n this.validatePath(path, pathId, operationIds);\n }\n });\n\n Object.keys(this.api.definitions || {}).forEach(definitionName => {\n const definition = this.api.definitions[definitionName];\n const definitionId = `/definitions/${definitionName}`;\n\n if (!/^[a-zA-Z0-9.\\-_]+$/.test(definitionName)) {\n this.reportError(\n `\\`${definitionId}\\` has an invalid name. Definition names should match against: /^[a-zA-Z0-9.-_]+$/`,\n );\n }\n\n this.validateRequiredPropertiesExist(definition, definitionId);\n });\n }\n\n /**\n * Validates the given path.\n *\n */\n private validatePath(path: OpenAPIV2.PathItemObject, pathId: string, operationIds: string[]) {\n swaggerHTTPMethods.forEach(operationName => {\n const operation = path[operationName];\n const operationId = `${pathId}/${operationName}`;\n\n if (operation) {\n const declaredOperationId = operation.operationId;\n if (declaredOperationId) {\n if (!operationIds.includes(declaredOperationId)) {\n operationIds.push(declaredOperationId);\n } else {\n this.reportError(`The operationId \\`${declaredOperationId}\\` is duplicated and must be made unique.`);\n }\n }\n\n this.validateParameters(path, pathId, operation, operationId);\n\n Object.keys(operation.responses || {}).forEach(responseName => {\n const response = operation.responses[responseName];\n if ('$ref' in response || !response) {\n return;\n }\n\n const responseId = `${operationId}/responses/${responseName}`;\n this.validateResponse(responseName, response, responseId);\n });\n }\n });\n }\n\n /**\n * Validates the parameters for the given operation.\n *\n */\n private validateParameters(\n path: OpenAPIV2.PathItemObject,\n pathId: string,\n operation: OpenAPIV2.OperationObject,\n operationId: string,\n ) {\n const pathParams = (path.parameters || []).filter(param => !('$ref' in param)) as OpenAPIV2.ParameterObject[];\n const operationParams = (operation.parameters || []).filter(\n param => !('$ref' in param),\n ) as OpenAPIV2.ParameterObject[];\n\n // Check for duplicate path parameters\n this.checkForDuplicates(pathParams, pathId);\n\n // Check for duplicate operation parameters\n this.checkForDuplicates(operationParams, operationId);\n\n // Combine the path and operation parameters,\n // with the operation params taking precedence over the path params\n const params = pathParams.reduce((combinedParams, value) => {\n const duplicate = combinedParams.some(param => {\n if ('$ref' in param || '$ref' in value) {\n return false;\n }\n return param.in === value.in && param.name === value.name;\n });\n if (!duplicate) {\n combinedParams.push(value);\n }\n return combinedParams;\n }, operationParams.slice());\n\n this.validateBodyParameters(params, operationId);\n this.validatePathParameters(params, pathId, operationId);\n this.validateParameterTypes(params, operation, operationId);\n }\n\n /**\n * Validates body and formData parameters for the given operation.\n *\n */\n private validateBodyParameters(params: OpenAPIV2.ParameterObject[], operationId: string) {\n const bodyParams = params.filter(param => param.in === 'body');\n const formParams = params.filter(param => param.in === 'formData');\n\n // There can only be one \"body\" parameter\n if (bodyParams.length > 1) {\n this.reportError(`\\`${operationId}\\` has ${bodyParams.length} body parameters. Only one is allowed.`);\n } else if (bodyParams.length > 0 && formParams.length > 0) {\n // \"body\" params and \"formData\" params are mutually exclusive\n this.reportError(\n `\\`${operationId}\\` has \\`body\\` and \\`formData\\` parameters. Only one or the other is allowed.`,\n );\n }\n }\n\n /**\n * Validates path parameters for the given path.\n *\n */\n private validatePathParameters(params: OpenAPIV2.ParameterObject[], pathId: string, operationId: string) {\n // Find all {placeholders} in the path string\n const placeholders: string[] = pathId.match(pathParameterTemplateRegExp) || [];\n\n // Check for duplicates\n for (let i = 0; i < placeholders.length; i++) {\n for (let j = i + 1; j < placeholders.length; j++) {\n if (placeholders[i] === placeholders[j]) {\n this.reportError(`\\`${operationId}\\` has multiple path placeholders named \\`${placeholders[i]}\\`.`);\n }\n }\n }\n\n params\n .filter(param => param.in === 'path')\n .forEach(param => {\n if (param.required !== true) {\n this.reportError(\n `Path parameters cannot be optional. Set \\`required=true\\` for the \\`${param.name}\\` parameter at \\`${operationId}\\`.`,\n );\n }\n\n const match = placeholders.indexOf(`{${param.name}}`);\n if (match === -1) {\n this.reportError(\n `\\`${operationId}\\` has a path parameter named \\`${param.name}\\`, but there is no corresponding \\`{${param.name}}\\` in the path string.`,\n );\n }\n\n placeholders.splice(match, 1);\n });\n\n if (placeholders.length > 0) {\n const list = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }).format(\n placeholders.map(placeholder => `\\`${placeholder}\\``),\n );\n\n this.reportError(`\\`${operationId}\\` is missing path parameter(s) for ${list}.`);\n }\n }\n\n /**\n * Validates data types of parameters for the given operation.\n *\n */\n private validateParameterTypes(\n params: OpenAPIV2.ParameterObject[],\n operation: OpenAPIV2.OperationObject,\n operationId: string,\n ) {\n params.forEach(param => {\n const parameterId = `${operationId}/parameters/${param.name}`;\n let schema;\n\n switch (param.in) {\n case 'body':\n schema = param.schema;\n break;\n case 'formData':\n schema = param;\n break;\n default:\n schema = param;\n }\n\n this.validateSchema(schema, parameterId);\n this.validateRequiredPropertiesExist(schema, parameterId);\n\n if (schema.type === 'file') {\n // \"file\" params must consume at least one of these MIME types\n const formData = /multipart\\/(.*\\+)?form-data/;\n const urlEncoded = /application\\/(.*\\+)?x-www-form-urlencoded/;\n\n const consumes = operation.consumes || this.api.consumes || [];\n\n const hasValidMimeType = consumes.some(consume => {\n return formData.test(consume) || urlEncoded.test(consume);\n });\n\n if (!hasValidMimeType) {\n this.reportError(\n `\\`${operationId}\\` has a file parameter, so it must consume \\`multipart/form-data\\` or \\`application/x-www-form-urlencoded\\`.`,\n );\n }\n }\n });\n }\n\n /**\n * Validates the given response object.\n *\n */\n private validateResponse(code: number | string, response: OpenAPIV2.ResponseObject, responseId: string) {\n /**\n * The Swagger JSON Schema allows for any HTTP code between `000` and `999`, where as the OpenAPI\n * JSON Schema fixed this to require it to be a _known_ code between 100 and 599.\n *\n * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/f9a2302ee6707cb65f4aaf180fdce9b0a906701e/schemas/v2.0/schema.json#L350}\n */\n if (code !== 'default') {\n if (\n (typeof code === 'number' && (code < 100 || code > 599)) ||\n (typeof code === 'string' && (Number(code) < 100 || Number(code) > 599))\n ) {\n this.reportError(`\\`${responseId}\\` has an invalid response code: ${code}`);\n }\n }\n\n Object.keys(response.headers || {}).forEach(headerName => {\n const header = response.headers[headerName];\n const headerId = `${responseId}/headers/${headerName}`;\n this.validateSchema(header, headerId);\n });\n\n if (response.schema) {\n if ('$ref' in response.schema) {\n return;\n }\n\n this.validateSchema(response.schema, `${responseId}/schema`);\n }\n }\n\n /**\n * Validates the given Swagger schema object.\n *\n */\n private validateSchema(schema: OpenAPIV2.SchemaObject, schemaId: string) {\n if (schema.type === 'array' && !schema.items) {\n this.reportError(`\\`${schemaId}\\` is an array, so it must include an \\`items\\` schema.`);\n }\n }\n\n /**\n * Validates that the declared properties of the given Swagger schema object actually exist.\n *\n */\n private validateRequiredPropertiesExist(schema: IJsonSchema, schemaId: string) {\n // Recursively collects all properties of the schema and its ancestors. They are added to the props object.\n function collectProperties(schemaObj: IJsonSchema, props: Record<string, IJsonSchema>) {\n if (schemaObj.properties) {\n Object.keys(schemaObj.properties).forEach(property => {\n // eslint-disable-next-line no-prototype-builtins\n if (schemaObj.properties.hasOwnProperty(property)) {\n // eslint-disable-next-line no-param-reassign\n props[property] = schemaObj.properties[property];\n }\n });\n }\n\n if (schemaObj.allOf) {\n schemaObj.allOf.forEach(parent => {\n collectProperties(parent, props);\n });\n }\n }\n\n if (schema.required && Array.isArray(schema.required)) {\n const props: Record<string, IJsonSchema> = {};\n collectProperties(schema, props);\n schema.required.forEach(requiredProperty => {\n if (!props[requiredProperty]) {\n this.reportError(\n `Property \\`${requiredProperty}\\` is listed as required but does not exist in \\`${schemaId}\\`.`,\n );\n }\n });\n }\n }\n\n /**\n * Checks the given parameter list for duplicates.\n *\n */\n private checkForDuplicates(params: OpenAPIV2.ParameterObject[], schemaId: string) {\n for (let i = 0; i < params.length - 1; i++) {\n const outer = params[i];\n for (let j = i + 1; j < params.length; j++) {\n const inner = params[j];\n if (outer.name === inner.name && outer.in === inner.in) {\n this.reportError(`Found multiple \\`${outer.in}\\` parameters named \\`${outer.name}\\` in \\`${schemaId}\\`.`);\n }\n }\n }\n }\n}\n","import type { ParserRulesOpenAPI, ValidationResult } from '../types.js';\nimport type { SpecificationValidator } from './spec/index.js';\nimport type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';\n\nimport { getSpecificationName, isOpenAPI } from '../lib/index.js';\n\nimport { OpenAPISpecificationValidator } from './spec/openapi.js';\nimport { SwaggerSpecificationValidator } from './spec/swagger.js';\n\n/**\n * Validates either a Swagger 2.0 or OpenAPI 3.x API definition against cases that aren't covered\n * by their JSON Schema definitions.\n *\n */\nexport function validateSpec(\n api: OpenAPIV2.Document | OpenAPIV3_1.Document | OpenAPIV3.Document,\n rules: {\n openapi: ParserRulesOpenAPI;\n },\n): ValidationResult {\n let validator: SpecificationValidator;\n\n const specificationName = getSpecificationName(api);\n if (isOpenAPI(api)) {\n validator = new OpenAPISpecificationValidator(api, rules.openapi);\n } else {\n validator = new SwaggerSpecificationValidator(api);\n }\n\n validator.run();\n\n if (!validator.errors.length) {\n return {\n valid: true,\n warnings: validator.warnings,\n specification: specificationName,\n };\n }\n\n return {\n valid: false,\n errors: validator.errors,\n warnings: validator.warnings,\n additionalErrors: 0,\n specification: specificationName,\n };\n}\n"]} |
+184
| import { JSONSchema4Object, JSONSchema6Object, JSONSchema7Object } from 'json-schema'; | ||
| import { OpenAPIV2, OpenAPIV3_1, OpenAPIV3 } from 'openapi-types'; | ||
| type APIDocument<T extends object = NonNullable<unknown>> = OpenAPIV2.Document<T> | OpenAPIV3_1.Document<T> | OpenAPIV3.Document<T>; | ||
| type JSONSchemaObject = JSONSchema4Object | JSONSchema6Object | JSONSchema7Object; | ||
| interface ErrorDetails { | ||
| message: string; | ||
| } | ||
| interface WarningDetails { | ||
| message: string; | ||
| } | ||
| type ValidationResult = { | ||
| valid: false; | ||
| errors: ErrorDetails[]; | ||
| warnings: WarningDetails[]; | ||
| additionalErrors: number; | ||
| specification: 'OpenAPI' | 'Swagger' | null; | ||
| } | { | ||
| valid: true; | ||
| warnings: WarningDetails[]; | ||
| specification: 'OpenAPI' | 'Swagger' | null; | ||
| }; | ||
| interface ParserRulesOpenAPI extends Record<string, 'error' | 'warning'> { | ||
| /** | ||
| * Schemas that are defined as `type: array` must also have an `items` schema. The default | ||
| * is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.4.md#json-schema-keywords} | ||
| */ | ||
| 'array-without-items': 'error' | 'warning'; | ||
| /** | ||
| * Parameters must be unique. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-operationparameters} | ||
| */ | ||
| 'duplicate-non-request-body-parameters': 'error' | 'warning'; | ||
| /** | ||
| * The `operationId` definition in a path object must be unique. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-operationid} | ||
| */ | ||
| 'duplicate-operation-id': 'error' | 'warning'; | ||
| /** | ||
| * Parameters that are defined within the path URI must be specified as being `required`. The | ||
| * default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-operationparameters} | ||
| */ | ||
| 'non-optional-path-parameters': 'error' | 'warning'; | ||
| /** | ||
| * Path parameters defined in a path URI path template must also be specified as part of that | ||
| * paths `parameters`. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-templating} | ||
| */ | ||
| 'path-parameters-not-in-parameters': 'error' | 'warning'; | ||
| /** | ||
| * Path parameters defined in `parameters` must also be specified in the path URI with | ||
| * path templating. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-parametername} | ||
| */ | ||
| 'path-parameters-not-in-path': 'error' | 'warning'; | ||
| } | ||
| interface ParserOptions { | ||
| dereference?: { | ||
| /** | ||
| * Determines whether circular `$ref` pointers are handled. | ||
| * | ||
| * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any | ||
| * circular references. If set to `ignore` then circular references will be ignored and their | ||
| * `$ref` pointers will be left alone; if you use this in conjunction with `onCircular` you | ||
| * will be able to see which paths in the schema contain circular references. | ||
| * | ||
| */ | ||
| circular?: boolean | 'ignore'; | ||
| /** | ||
| * Callback invoked during circular reference detection. | ||
| * | ||
| * @param path - The path that is circular (ie. the `$ref` string) | ||
| */ | ||
| onCircular?(path: string): void; | ||
| /** | ||
| * Callback invoked during dereferencing. | ||
| * | ||
| * @param path - The path being dereferenced (ie. the `$ref` string). | ||
| * @param value - The JSON Schema that the `$ref` resolved to. | ||
| * @param parent - The parent of the dereferenced object. | ||
| * @param parentPropName - The prop name of the parent object whose value was dereferenced. | ||
| */ | ||
| onDereference?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void; | ||
| }; | ||
| resolve?: { | ||
| /** | ||
| * Determines whether external $ref pointers will be resolved. If this option is disabled, then | ||
| * external `$ref` pointers will simply be ignored. | ||
| */ | ||
| external?: boolean; | ||
| http?: { | ||
| /** | ||
| * The amount of time (in milliseconds) to wait for a response from a server when downloading | ||
| * an API definition. The default is 5 seconds. | ||
| */ | ||
| timeout?: number; | ||
| }; | ||
| }; | ||
| validate?: { | ||
| errors?: { | ||
| /** | ||
| * Configures if you want validation errors that are thrown to be colorized. The default is | ||
| * `false`. | ||
| */ | ||
| colorize?: boolean; | ||
| }; | ||
| rules?: { | ||
| openapi?: Partial<ParserRulesOpenAPI>; | ||
| /** | ||
| * Swagger validation rules cannot be configured and are always treated as errors. | ||
| */ | ||
| swagger?: never; | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Parses the given API definition, in JSON or YAML format, and returns it as a JSON object. This | ||
| * method **does not** resolve `$ref` pointers or dereference anything. It simply parses _one_ file | ||
| * and returns it. | ||
| * | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function parse<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S>; | ||
| /** | ||
| * Bundles all referenced files and URLs into a single API definition that only has _internal_ | ||
| * `$ref` pointers. This lets you split up your definition however you want while you're building | ||
| * it, but later combine all those files together when it's time to package or distribute the API | ||
| * definition to other people. The resulting definition size will be small, since it will still | ||
| * contain _internal_ JSON references rather than being fully-dereferenced. | ||
| * | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function bundle<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S>; | ||
| /** | ||
| * Dereferences all `$ref` pointers in the supplied API definition, replacing each reference with | ||
| * its resolved value. This results in an API definition that does not contain _any_ `$ref` | ||
| * pointers. Instead, it's a normal JSON object tree that can easily be crawled and used just like | ||
| * any other object. This is great for programmatic usage, especially when using tools that don't | ||
| * understand JSON references. | ||
| * | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function dereference<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S>; | ||
| /** | ||
| * Validates the API definition against the Swagger 2.0, OpenAPI 3.0, or OpenAPI 3.1 specifications. | ||
| * | ||
| * In addition to validating the API definition against their respective specification schemas it | ||
| * will also be validated against specific areas that aren't covered by the Swagger or OpenAPI | ||
| * schemas, such as duplicate parameters, invalid component schema names, or duplicate | ||
| * `operationId` values. | ||
| * | ||
| * If validation fails an error will be thrown with information about what, and where, the error | ||
| * lies within the API definition. | ||
| * | ||
| * Internally this method invokes [`dereference()`](#dereference) so the returned object, whether | ||
| * its a Swagger or OpenAPI definition, will be fully dereferenced. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v2.0} | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.0} | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.1} | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function validate<S extends APIDocument, Options extends ParserOptions>(api: S | string, options?: Options): Promise<ValidationResult>; | ||
| /** | ||
| * A utility to transform the `ValidationResult` from a `validate()` call into a human-readable | ||
| * string. | ||
| * | ||
| */ | ||
| declare function compileErrors(result: ValidationResult): string; | ||
| export { type ErrorDetails, type ParserOptions, type ValidationResult, type WarningDetails, bundle, compileErrors, dereference, parse, validate }; |
+184
| import { JSONSchema4Object, JSONSchema6Object, JSONSchema7Object } from 'json-schema'; | ||
| import { OpenAPIV2, OpenAPIV3_1, OpenAPIV3 } from 'openapi-types'; | ||
| type APIDocument<T extends object = NonNullable<unknown>> = OpenAPIV2.Document<T> | OpenAPIV3_1.Document<T> | OpenAPIV3.Document<T>; | ||
| type JSONSchemaObject = JSONSchema4Object | JSONSchema6Object | JSONSchema7Object; | ||
| interface ErrorDetails { | ||
| message: string; | ||
| } | ||
| interface WarningDetails { | ||
| message: string; | ||
| } | ||
| type ValidationResult = { | ||
| valid: false; | ||
| errors: ErrorDetails[]; | ||
| warnings: WarningDetails[]; | ||
| additionalErrors: number; | ||
| specification: 'OpenAPI' | 'Swagger' | null; | ||
| } | { | ||
| valid: true; | ||
| warnings: WarningDetails[]; | ||
| specification: 'OpenAPI' | 'Swagger' | null; | ||
| }; | ||
| interface ParserRulesOpenAPI extends Record<string, 'error' | 'warning'> { | ||
| /** | ||
| * Schemas that are defined as `type: array` must also have an `items` schema. The default | ||
| * is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.4.md#json-schema-keywords} | ||
| */ | ||
| 'array-without-items': 'error' | 'warning'; | ||
| /** | ||
| * Parameters must be unique. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-operationparameters} | ||
| */ | ||
| 'duplicate-non-request-body-parameters': 'error' | 'warning'; | ||
| /** | ||
| * The `operationId` definition in a path object must be unique. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-operationid} | ||
| */ | ||
| 'duplicate-operation-id': 'error' | 'warning'; | ||
| /** | ||
| * Parameters that are defined within the path URI must be specified as being `required`. The | ||
| * default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-operationparameters} | ||
| */ | ||
| 'non-optional-path-parameters': 'error' | 'warning'; | ||
| /** | ||
| * Path parameters defined in a path URI path template must also be specified as part of that | ||
| * paths `parameters`. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#path-templating} | ||
| */ | ||
| 'path-parameters-not-in-parameters': 'error' | 'warning'; | ||
| /** | ||
| * Path parameters defined in `parameters` must also be specified in the path URI with | ||
| * path templating. The default is `error`. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-parametername} | ||
| */ | ||
| 'path-parameters-not-in-path': 'error' | 'warning'; | ||
| } | ||
| interface ParserOptions { | ||
| dereference?: { | ||
| /** | ||
| * Determines whether circular `$ref` pointers are handled. | ||
| * | ||
| * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any | ||
| * circular references. If set to `ignore` then circular references will be ignored and their | ||
| * `$ref` pointers will be left alone; if you use this in conjunction with `onCircular` you | ||
| * will be able to see which paths in the schema contain circular references. | ||
| * | ||
| */ | ||
| circular?: boolean | 'ignore'; | ||
| /** | ||
| * Callback invoked during circular reference detection. | ||
| * | ||
| * @param path - The path that is circular (ie. the `$ref` string) | ||
| */ | ||
| onCircular?(path: string): void; | ||
| /** | ||
| * Callback invoked during dereferencing. | ||
| * | ||
| * @param path - The path being dereferenced (ie. the `$ref` string). | ||
| * @param value - The JSON Schema that the `$ref` resolved to. | ||
| * @param parent - The parent of the dereferenced object. | ||
| * @param parentPropName - The prop name of the parent object whose value was dereferenced. | ||
| */ | ||
| onDereference?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void; | ||
| }; | ||
| resolve?: { | ||
| /** | ||
| * Determines whether external $ref pointers will be resolved. If this option is disabled, then | ||
| * external `$ref` pointers will simply be ignored. | ||
| */ | ||
| external?: boolean; | ||
| http?: { | ||
| /** | ||
| * The amount of time (in milliseconds) to wait for a response from a server when downloading | ||
| * an API definition. The default is 5 seconds. | ||
| */ | ||
| timeout?: number; | ||
| }; | ||
| }; | ||
| validate?: { | ||
| errors?: { | ||
| /** | ||
| * Configures if you want validation errors that are thrown to be colorized. The default is | ||
| * `false`. | ||
| */ | ||
| colorize?: boolean; | ||
| }; | ||
| rules?: { | ||
| openapi?: Partial<ParserRulesOpenAPI>; | ||
| /** | ||
| * Swagger validation rules cannot be configured and are always treated as errors. | ||
| */ | ||
| swagger?: never; | ||
| }; | ||
| }; | ||
| } | ||
| /** | ||
| * Parses the given API definition, in JSON or YAML format, and returns it as a JSON object. This | ||
| * method **does not** resolve `$ref` pointers or dereference anything. It simply parses _one_ file | ||
| * and returns it. | ||
| * | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function parse<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S>; | ||
| /** | ||
| * Bundles all referenced files and URLs into a single API definition that only has _internal_ | ||
| * `$ref` pointers. This lets you split up your definition however you want while you're building | ||
| * it, but later combine all those files together when it's time to package or distribute the API | ||
| * definition to other people. The resulting definition size will be small, since it will still | ||
| * contain _internal_ JSON references rather than being fully-dereferenced. | ||
| * | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function bundle<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S>; | ||
| /** | ||
| * Dereferences all `$ref` pointers in the supplied API definition, replacing each reference with | ||
| * its resolved value. This results in an API definition that does not contain _any_ `$ref` | ||
| * pointers. Instead, it's a normal JSON object tree that can easily be crawled and used just like | ||
| * any other object. This is great for programmatic usage, especially when using tools that don't | ||
| * understand JSON references. | ||
| * | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function dereference<S extends APIDocument = APIDocument>(api: S | string, options?: ParserOptions): Promise<S>; | ||
| /** | ||
| * Validates the API definition against the Swagger 2.0, OpenAPI 3.0, or OpenAPI 3.1 specifications. | ||
| * | ||
| * In addition to validating the API definition against their respective specification schemas it | ||
| * will also be validated against specific areas that aren't covered by the Swagger or OpenAPI | ||
| * schemas, such as duplicate parameters, invalid component schema names, or duplicate | ||
| * `operationId` values. | ||
| * | ||
| * If validation fails an error will be thrown with information about what, and where, the error | ||
| * lies within the API definition. | ||
| * | ||
| * Internally this method invokes [`dereference()`](#dereference) so the returned object, whether | ||
| * its a Swagger or OpenAPI definition, will be fully dereferenced. | ||
| * | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v2.0} | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.0} | ||
| * @see {@link https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.1} | ||
| * @param api - A file path or URL to a JSON Schema object, or the JSON Schema object itself. | ||
| * @param options | ||
| */ | ||
| declare function validate<S extends APIDocument, Options extends ParserOptions>(api: S | string, options?: Options): Promise<ValidationResult>; | ||
| /** | ||
| * A utility to transform the `ValidationResult` from a `validate()` call into a human-readable | ||
| * string. | ||
| * | ||
| */ | ||
| declare function compileErrors(result: ValidationResult): string; | ||
| export { type ErrorDetails, type ParserOptions, type ValidationResult, type WarningDetails, bundle, compileErrors, dereference, parse, validate }; |
+834
| // src/index.ts | ||
| import $RefParser, { dereferenceInternal, MissingPointerError } from "@apidevtools/json-schema-ref-parser"; | ||
| // src/lib/index.ts | ||
| var pathParameterTemplateRegExp = /\{([^/}]+)}/g; | ||
| var supportedHTTPMethods = ["get", "post", "put", "delete", "patch", "options", "head", "trace"]; | ||
| var swaggerHTTPMethods = ["get", "put", "post", "delete", "options", "head", "patch"]; | ||
| function isSwagger(schema) { | ||
| return "swagger" in schema && schema.swagger !== void 0; | ||
| } | ||
| function isOpenAPI(schema) { | ||
| return "openapi" in schema && schema.openapi !== void 0; | ||
| } | ||
| function isOpenAPI30(schema) { | ||
| return "openapi" in schema && schema.openapi !== void 0 && schema.openapi.startsWith("3.0"); | ||
| } | ||
| function isOpenAPI31(schema) { | ||
| return "openapi" in schema && schema.openapi !== void 0 && schema.openapi.startsWith("3.1"); | ||
| } | ||
| function getSpecificationName(api) { | ||
| return isSwagger(api) ? "Swagger" : "OpenAPI"; | ||
| } | ||
| // src/util.ts | ||
| import { getJsonSchemaRefParserDefaultOptions } from "@apidevtools/json-schema-ref-parser"; | ||
| // src/repair.ts | ||
| function fixServers(server, path) { | ||
| if (server && "url" in server && server.url && server.url.startsWith("/")) { | ||
| try { | ||
| const inUrl = new URL(path); | ||
| server.url = `${inUrl.protocol}//${inUrl.hostname}${server.url}`; | ||
| } catch { | ||
| } | ||
| } | ||
| } | ||
| function fixOasRelativeServers(schema, filePath) { | ||
| if (!schema || !isOpenAPI(schema) || !filePath || !filePath.startsWith("http:") && !filePath.startsWith("https:")) { | ||
| return; | ||
| } | ||
| if (schema.servers) { | ||
| schema.servers.map((server) => fixServers(server, filePath)); | ||
| } | ||
| ["paths", "webhooks"].forEach((component) => { | ||
| if (component in schema) { | ||
| const schemaElement = schema.paths || {}; | ||
| Object.keys(schemaElement).forEach((path) => { | ||
| const pathItem = schemaElement[path] || {}; | ||
| Object.keys(pathItem).forEach((opItem) => { | ||
| const pathItemElement = pathItem[opItem]; | ||
| if (!pathItemElement) { | ||
| return; | ||
| } | ||
| if (opItem === "servers" && Array.isArray(pathItemElement)) { | ||
| pathItemElement.forEach((server) => fixServers(server, filePath)); | ||
| return; | ||
| } | ||
| if (supportedHTTPMethods.includes(opItem) && typeof pathItemElement === "object" && "servers" in pathItemElement && Array.isArray(pathItemElement.servers)) { | ||
| pathItemElement.servers.forEach((server) => fixServers(server, filePath)); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| // src/util.ts | ||
| function repairSchema(schema, filePath) { | ||
| if (isOpenAPI(schema)) { | ||
| fixOasRelativeServers(schema, filePath); | ||
| } | ||
| } | ||
| function normalizeArguments(api) { | ||
| return { | ||
| path: typeof api === "string" ? api : "", | ||
| schema: typeof api === "object" ? api : void 0 | ||
| }; | ||
| } | ||
| function convertOptionsForParser(options) { | ||
| const parserOptions = getJsonSchemaRefParserDefaultOptions(); | ||
| return { | ||
| ...parserOptions, | ||
| dereference: { | ||
| ...parserOptions.dereference, | ||
| circular: options?.dereference && "circular" in options.dereference ? options.dereference.circular : parserOptions.dereference.circular, | ||
| onCircular: options?.dereference?.onCircular || parserOptions.dereference.onCircular, | ||
| onDereference: options?.dereference?.onDereference || parserOptions.dereference.onDereference, | ||
| // OpenAPI 3.1 allows for `summary` and `description` properties at the same level as a `$ref` | ||
| // pointer to be preserved when that `$ref` pointer is dereferenced. The default behavior of | ||
| // `json-schema-ref-parser` is to discard these properties but this option allows us to | ||
| // override that behavior. | ||
| preservedProperties: ["summary", "description"] | ||
| }, | ||
| resolve: { | ||
| ...parserOptions.resolve, | ||
| external: options?.resolve && "external" in options.resolve ? options.resolve.external : parserOptions.resolve.external, | ||
| http: { | ||
| ...typeof parserOptions.resolve.http === "object" ? parserOptions.resolve.http : {}, | ||
| timeout: options?.resolve?.http && "timeout" in options.resolve.http ? options.resolve.http.timeout : 5e3 | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| // src/validators/schema.ts | ||
| import betterAjvErrors from "@readme/better-ajv-errors"; | ||
| import { openapi } from "@readme/openapi-schemas"; | ||
| import Ajv from "ajv/dist/2020.js"; | ||
| import AjvDraft4 from "ajv-draft-04"; | ||
| // src/lib/reduceAjvErrors.ts | ||
| function reduceAjvErrors(errors) { | ||
| const flattened = /* @__PURE__ */ new Map(); | ||
| errors.forEach((err) => { | ||
| if (["must have required property '$ref'", "must match exactly one schema in oneOf"].includes(err.message)) { | ||
| return; | ||
| } | ||
| if (!flattened.size) { | ||
| flattened.set(err.instancePath, err); | ||
| return; | ||
| } else if (flattened.has(err.instancePath)) { | ||
| return; | ||
| } | ||
| let shouldRecordError = true; | ||
| flattened.forEach((flat) => { | ||
| if (flat.instancePath.includes(err.instancePath)) { | ||
| shouldRecordError = false; | ||
| } | ||
| }); | ||
| if (shouldRecordError) { | ||
| flattened.set(err.instancePath, err); | ||
| } | ||
| }); | ||
| if (!flattened.size) { | ||
| return errors; | ||
| } | ||
| return [...flattened.values()]; | ||
| } | ||
| // src/validators/schema.ts | ||
| var LARGE_SPEC_ERROR_CAP = 20; | ||
| var LARGE_SPEC_SIZE_CAP = 5e6; | ||
| function initializeAjv(draft04 = true) { | ||
| const opts = { | ||
| allErrors: true, | ||
| strict: false, | ||
| validateFormats: false | ||
| }; | ||
| if (draft04) { | ||
| return new AjvDraft4(opts); | ||
| } | ||
| return new Ajv(opts); | ||
| } | ||
| function validateSchema(api, options = {}) { | ||
| let ajv; | ||
| let schema; | ||
| const specificationName = getSpecificationName(api); | ||
| if (isSwagger(api)) { | ||
| schema = openapi.v2; | ||
| ajv = initializeAjv(); | ||
| } else if (isOpenAPI31(api)) { | ||
| schema = openapi.v31legacy; | ||
| const schemaDynamicRef = schema.$defs.schema; | ||
| if ("$dynamicAnchor" in schemaDynamicRef) { | ||
| delete schemaDynamicRef.$dynamicAnchor; | ||
| } | ||
| schema.$defs.components.properties.schemas.additionalProperties = schemaDynamicRef; | ||
| schema.$defs.header.dependentSchemas.schema.properties.schema = schemaDynamicRef; | ||
| schema.$defs["media-type"].properties.schema = schemaDynamicRef; | ||
| schema.$defs.parameter.properties.schema = schemaDynamicRef; | ||
| ajv = initializeAjv(false); | ||
| } else { | ||
| schema = openapi.v3; | ||
| ajv = initializeAjv(); | ||
| } | ||
| const isValid = ajv.validate(schema, api); | ||
| if (isValid) { | ||
| return { valid: true, warnings: [], specification: specificationName }; | ||
| } | ||
| let additionalErrors = 0; | ||
| let reducedErrors = reduceAjvErrors(ajv.errors); | ||
| if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) { | ||
| try { | ||
| if (JSON.stringify(api).length >= LARGE_SPEC_SIZE_CAP) { | ||
| additionalErrors = reducedErrors.length - 20; | ||
| reducedErrors = reducedErrors.slice(0, 20); | ||
| } | ||
| } catch (error) { | ||
| } | ||
| } | ||
| try { | ||
| const errors = betterAjvErrors(schema, api, reducedErrors, { | ||
| format: "cli-array", | ||
| colorize: options?.validate?.errors?.colorize || false, | ||
| indent: 2 | ||
| }); | ||
| return { | ||
| valid: false, | ||
| errors, | ||
| warnings: [], | ||
| additionalErrors, | ||
| specification: specificationName | ||
| }; | ||
| } catch (err) { | ||
| return { | ||
| valid: false, | ||
| errors: [{ message: err.message }], | ||
| warnings: [], | ||
| additionalErrors, | ||
| specification: specificationName | ||
| }; | ||
| } | ||
| } | ||
| // src/validators/spec/index.ts | ||
| var SpecificationValidator = class { | ||
| errors = []; | ||
| warnings = []; | ||
| reportError(message) { | ||
| this.errors.push({ message }); | ||
| } | ||
| reportWarning(message) { | ||
| this.warnings.push({ message }); | ||
| } | ||
| }; | ||
| // src/validators/spec/openapi.ts | ||
| var OpenAPISpecificationValidator = class extends SpecificationValidator { | ||
| api; | ||
| rules; | ||
| constructor(api, rules) { | ||
| super(); | ||
| this.api = api; | ||
| this.rules = rules; | ||
| } | ||
| run() { | ||
| const operationIds = []; | ||
| Object.keys(this.api.paths || {}).forEach((pathName) => { | ||
| const path = this.api.paths[pathName]; | ||
| const pathId = `/paths${pathName}`; | ||
| if (path && pathName.startsWith("/")) { | ||
| this.validatePath(path, pathId, operationIds); | ||
| } | ||
| }); | ||
| if (isOpenAPI30(this.api)) { | ||
| if (this.api.components) { | ||
| Object.keys(this.api.components).forEach((componentType) => { | ||
| Object.keys(this.api.components[componentType]).forEach((componentName) => { | ||
| if (!/^[a-zA-Z0-9.\-_]+$/.test(componentName)) { | ||
| const componentId = `/components/${componentType}/${componentName}`; | ||
| this.reportError( | ||
| `\`${componentId}\` has an invalid name. Component names should match against: /^[a-zA-Z0-9.-_]+$/` | ||
| ); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| if (isOpenAPI31(this.api)) { | ||
| if (!Object.keys(this.api.paths || {}).length && !Object.keys(this.api.webhooks || {}).length) { | ||
| this.reportError("OpenAPI 3.1 definitions must contain at least one entry in either `paths` or `webhook`."); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given path. | ||
| * | ||
| */ | ||
| validatePath(path, pathId, operationIds) { | ||
| supportedHTTPMethods.forEach((operationName) => { | ||
| const operation = path[operationName]; | ||
| const operationId = `${pathId}/${operationName}`; | ||
| if (operation) { | ||
| const declaredOperationId = operation.operationId; | ||
| if (declaredOperationId) { | ||
| if (!operationIds.includes(declaredOperationId)) { | ||
| operationIds.push(declaredOperationId); | ||
| } else if (this.rules["duplicate-operation-id"] === "warning") { | ||
| this.reportWarning(`The operationId \`${declaredOperationId}\` is duplicated and should be made unique.`); | ||
| } else { | ||
| this.reportError(`The operationId \`${declaredOperationId}\` is duplicated and must be made unique.`); | ||
| } | ||
| } | ||
| this.validateParameters(path, pathId, operation, operationId); | ||
| Object.keys(operation.responses || {}).forEach((responseCode) => { | ||
| const response = operation.responses[responseCode]; | ||
| const responseId = `${operationId}/responses/${responseCode}`; | ||
| if (response && !("$ref" in response)) { | ||
| this.validateResponse(response, responseId); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameters(path, pathId, operation, operationId) { | ||
| const pathParams = path.parameters || []; | ||
| const operationParams = operation.parameters || []; | ||
| this.checkForDuplicates(pathParams, pathId); | ||
| this.checkForDuplicates(operationParams, operationId); | ||
| const params = pathParams.reduce((combinedParams, value) => { | ||
| const duplicate = combinedParams.some((param) => { | ||
| if ("$ref" in param || "$ref" in value) { | ||
| return false; | ||
| } | ||
| return param.in === value.in && param.name === value.name; | ||
| }); | ||
| if (!duplicate) { | ||
| combinedParams.push(value); | ||
| } | ||
| return combinedParams; | ||
| }, operationParams.slice()); | ||
| this.validatePathParameters(params, pathId, operationId); | ||
| this.validateParameterTypes(params, operationId); | ||
| } | ||
| /** | ||
| * Validates path parameters for the given path. | ||
| * | ||
| */ | ||
| validatePathParameters(params, pathId, operationId) { | ||
| const placeholders = [...new Set(pathId.match(pathParameterTemplateRegExp) || [])]; | ||
| params.filter((param) => "in" in param).filter((param) => param.in === "path").forEach((param) => { | ||
| if (param.required !== true) { | ||
| if (this.rules["non-optional-path-parameters"] === "warning") { | ||
| this.reportWarning( | ||
| `Path parameters should not be optional. Set \`required=true\` for the \`${param.name}\` parameter at \`${operationId}\`.` | ||
| ); | ||
| } else { | ||
| this.reportError( | ||
| `Path parameters cannot be optional. Set \`required=true\` for the \`${param.name}\` parameter at \`${operationId}\`.` | ||
| ); | ||
| } | ||
| } | ||
| const match = placeholders.indexOf(`{${param.name}}`); | ||
| if (match === -1) { | ||
| const error = `\`${operationId}\` has a path parameter named \`${param.name}\`, but there is no corresponding \`{${param.name}}\` in the path string.`; | ||
| if (this.rules["path-parameters-not-in-path"] === "warning") { | ||
| this.reportWarning(error); | ||
| } else { | ||
| this.reportError(error); | ||
| } | ||
| } | ||
| placeholders.splice(match, 1); | ||
| }); | ||
| if (placeholders.length > 0) { | ||
| const list = new Intl.ListFormat("en", { style: "long", type: "conjunction" }).format( | ||
| placeholders.map((placeholder) => `\`${placeholder}\``) | ||
| ); | ||
| const error = `\`${operationId}\` is missing path parameter(s) for ${list}.`; | ||
| if (this.rules["path-parameters-not-in-parameters"] === "warning") { | ||
| this.reportWarning(error); | ||
| } else { | ||
| this.reportError(error); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates data types of parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameterTypes(params, operationId) { | ||
| params.forEach((param) => { | ||
| if ("$ref" in param) { | ||
| return; | ||
| } | ||
| if (!param.schema && param.content) { | ||
| return; | ||
| } else if ("$ref" in param.schema) { | ||
| return; | ||
| } | ||
| const parameterId = `${operationId}/parameters/${param.name}`; | ||
| this.validateSchema(param.schema, parameterId); | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given response object. | ||
| * | ||
| */ | ||
| validateResponse(response, responseId) { | ||
| Object.keys(response.headers || {}).forEach((headerName) => { | ||
| const header = response.headers[headerName]; | ||
| const headerId = `${responseId}/headers/${headerName}`; | ||
| if ("$ref" in header) { | ||
| return; | ||
| } | ||
| if (header.schema) { | ||
| if (!("$ref" in header.schema)) { | ||
| this.validateSchema(header.schema, headerId); | ||
| } | ||
| } else if (header.content) { | ||
| Object.keys(header.content).forEach((mediaType) => { | ||
| if (header.content[mediaType].schema) { | ||
| if (!("$ref" in header.content[mediaType].schema)) { | ||
| this.validateSchema(header.content[mediaType].schema || {}, `${headerId}/content/${mediaType}/schema`); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| if (response.content) { | ||
| Object.keys(response.content).forEach((mediaType) => { | ||
| if (response.content[mediaType].schema) { | ||
| if (!("$ref" in response.content[mediaType].schema)) { | ||
| this.validateSchema(response.content[mediaType].schema || {}, `${responseId}/content/${mediaType}/schema`); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given Swagger schema object. | ||
| * | ||
| */ | ||
| validateSchema(schema, schemaId) { | ||
| if (schema.type === "array" && !schema.items) { | ||
| if (this.rules["array-without-items"] === "warning") { | ||
| this.reportWarning(`\`${schemaId}\` is an array, so it should include an \`items\` schema.`); | ||
| } else { | ||
| this.reportError(`\`${schemaId}\` is an array, so it must include an \`items\` schema.`); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Checks the given parameter list for duplicates. | ||
| * | ||
| */ | ||
| checkForDuplicates(params, schemaId) { | ||
| for (let i = 0; i < params.length - 1; i++) { | ||
| const outer = params[i]; | ||
| for (let j = i + 1; j < params.length; j++) { | ||
| const inner = params[j]; | ||
| if ("$ref" in outer || "$ref" in inner) { | ||
| continue; | ||
| } | ||
| if (outer.name === inner.name && outer.in === inner.in) { | ||
| const error = `Found multiple \`${outer.in}\` parameters named \`${outer.name}\` in \`${schemaId}\`.`; | ||
| if (this.rules["duplicate-non-request-body-parameters"] === "warning") { | ||
| this.reportWarning(error); | ||
| } else { | ||
| this.reportError(error); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| // src/validators/spec/swagger.ts | ||
| var SwaggerSpecificationValidator = class extends SpecificationValidator { | ||
| api; | ||
| constructor(api) { | ||
| super(); | ||
| this.api = api; | ||
| } | ||
| run() { | ||
| const operationIds = []; | ||
| Object.keys(this.api.paths || {}).forEach((pathName) => { | ||
| const path = this.api.paths[pathName]; | ||
| const pathId = `/paths${pathName}`; | ||
| if (path && pathName.startsWith("/")) { | ||
| this.validatePath(path, pathId, operationIds); | ||
| } | ||
| }); | ||
| Object.keys(this.api.definitions || {}).forEach((definitionName) => { | ||
| const definition = this.api.definitions[definitionName]; | ||
| const definitionId = `/definitions/${definitionName}`; | ||
| if (!/^[a-zA-Z0-9.\-_]+$/.test(definitionName)) { | ||
| this.reportError( | ||
| `\`${definitionId}\` has an invalid name. Definition names should match against: /^[a-zA-Z0-9.-_]+$/` | ||
| ); | ||
| } | ||
| this.validateRequiredPropertiesExist(definition, definitionId); | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given path. | ||
| * | ||
| */ | ||
| validatePath(path, pathId, operationIds) { | ||
| swaggerHTTPMethods.forEach((operationName) => { | ||
| const operation = path[operationName]; | ||
| const operationId = `${pathId}/${operationName}`; | ||
| if (operation) { | ||
| const declaredOperationId = operation.operationId; | ||
| if (declaredOperationId) { | ||
| if (!operationIds.includes(declaredOperationId)) { | ||
| operationIds.push(declaredOperationId); | ||
| } else { | ||
| this.reportError(`The operationId \`${declaredOperationId}\` is duplicated and must be made unique.`); | ||
| } | ||
| } | ||
| this.validateParameters(path, pathId, operation, operationId); | ||
| Object.keys(operation.responses || {}).forEach((responseName) => { | ||
| const response = operation.responses[responseName]; | ||
| if ("$ref" in response || !response) { | ||
| return; | ||
| } | ||
| const responseId = `${operationId}/responses/${responseName}`; | ||
| this.validateResponse(responseName, response, responseId); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameters(path, pathId, operation, operationId) { | ||
| const pathParams = (path.parameters || []).filter((param) => !("$ref" in param)); | ||
| const operationParams = (operation.parameters || []).filter( | ||
| (param) => !("$ref" in param) | ||
| ); | ||
| this.checkForDuplicates(pathParams, pathId); | ||
| this.checkForDuplicates(operationParams, operationId); | ||
| const params = pathParams.reduce((combinedParams, value) => { | ||
| const duplicate = combinedParams.some((param) => { | ||
| if ("$ref" in param || "$ref" in value) { | ||
| return false; | ||
| } | ||
| return param.in === value.in && param.name === value.name; | ||
| }); | ||
| if (!duplicate) { | ||
| combinedParams.push(value); | ||
| } | ||
| return combinedParams; | ||
| }, operationParams.slice()); | ||
| this.validateBodyParameters(params, operationId); | ||
| this.validatePathParameters(params, pathId, operationId); | ||
| this.validateParameterTypes(params, operation, operationId); | ||
| } | ||
| /** | ||
| * Validates body and formData parameters for the given operation. | ||
| * | ||
| */ | ||
| validateBodyParameters(params, operationId) { | ||
| const bodyParams = params.filter((param) => param.in === "body"); | ||
| const formParams = params.filter((param) => param.in === "formData"); | ||
| if (bodyParams.length > 1) { | ||
| this.reportError(`\`${operationId}\` has ${bodyParams.length} body parameters. Only one is allowed.`); | ||
| } else if (bodyParams.length > 0 && formParams.length > 0) { | ||
| this.reportError( | ||
| `\`${operationId}\` has \`body\` and \`formData\` parameters. Only one or the other is allowed.` | ||
| ); | ||
| } | ||
| } | ||
| /** | ||
| * Validates path parameters for the given path. | ||
| * | ||
| */ | ||
| validatePathParameters(params, pathId, operationId) { | ||
| const placeholders = pathId.match(pathParameterTemplateRegExp) || []; | ||
| for (let i = 0; i < placeholders.length; i++) { | ||
| for (let j = i + 1; j < placeholders.length; j++) { | ||
| if (placeholders[i] === placeholders[j]) { | ||
| this.reportError(`\`${operationId}\` has multiple path placeholders named \`${placeholders[i]}\`.`); | ||
| } | ||
| } | ||
| } | ||
| params.filter((param) => param.in === "path").forEach((param) => { | ||
| if (param.required !== true) { | ||
| this.reportError( | ||
| `Path parameters cannot be optional. Set \`required=true\` for the \`${param.name}\` parameter at \`${operationId}\`.` | ||
| ); | ||
| } | ||
| const match = placeholders.indexOf(`{${param.name}}`); | ||
| if (match === -1) { | ||
| this.reportError( | ||
| `\`${operationId}\` has a path parameter named \`${param.name}\`, but there is no corresponding \`{${param.name}}\` in the path string.` | ||
| ); | ||
| } | ||
| placeholders.splice(match, 1); | ||
| }); | ||
| if (placeholders.length > 0) { | ||
| const list = new Intl.ListFormat("en", { style: "long", type: "conjunction" }).format( | ||
| placeholders.map((placeholder) => `\`${placeholder}\``) | ||
| ); | ||
| this.reportError(`\`${operationId}\` is missing path parameter(s) for ${list}.`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates data types of parameters for the given operation. | ||
| * | ||
| */ | ||
| validateParameterTypes(params, operation, operationId) { | ||
| params.forEach((param) => { | ||
| const parameterId = `${operationId}/parameters/${param.name}`; | ||
| let schema; | ||
| switch (param.in) { | ||
| case "body": | ||
| schema = param.schema; | ||
| break; | ||
| case "formData": | ||
| schema = param; | ||
| break; | ||
| default: | ||
| schema = param; | ||
| } | ||
| this.validateSchema(schema, parameterId); | ||
| this.validateRequiredPropertiesExist(schema, parameterId); | ||
| if (schema.type === "file") { | ||
| const formData = /multipart\/(.*\+)?form-data/; | ||
| const urlEncoded = /application\/(.*\+)?x-www-form-urlencoded/; | ||
| const consumes = operation.consumes || this.api.consumes || []; | ||
| const hasValidMimeType = consumes.some((consume) => { | ||
| return formData.test(consume) || urlEncoded.test(consume); | ||
| }); | ||
| if (!hasValidMimeType) { | ||
| this.reportError( | ||
| `\`${operationId}\` has a file parameter, so it must consume \`multipart/form-data\` or \`application/x-www-form-urlencoded\`.` | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given response object. | ||
| * | ||
| */ | ||
| validateResponse(code, response, responseId) { | ||
| if (code !== "default") { | ||
| if (typeof code === "number" && (code < 100 || code > 599) || typeof code === "string" && (Number(code) < 100 || Number(code) > 599)) { | ||
| this.reportError(`\`${responseId}\` has an invalid response code: ${code}`); | ||
| } | ||
| } | ||
| Object.keys(response.headers || {}).forEach((headerName) => { | ||
| const header = response.headers[headerName]; | ||
| const headerId = `${responseId}/headers/${headerName}`; | ||
| this.validateSchema(header, headerId); | ||
| }); | ||
| if (response.schema) { | ||
| if ("$ref" in response.schema) { | ||
| return; | ||
| } | ||
| this.validateSchema(response.schema, `${responseId}/schema`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given Swagger schema object. | ||
| * | ||
| */ | ||
| validateSchema(schema, schemaId) { | ||
| if (schema.type === "array" && !schema.items) { | ||
| this.reportError(`\`${schemaId}\` is an array, so it must include an \`items\` schema.`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates that the declared properties of the given Swagger schema object actually exist. | ||
| * | ||
| */ | ||
| validateRequiredPropertiesExist(schema, schemaId) { | ||
| function collectProperties(schemaObj, props) { | ||
| if (schemaObj.properties) { | ||
| Object.keys(schemaObj.properties).forEach((property) => { | ||
| if (schemaObj.properties.hasOwnProperty(property)) { | ||
| props[property] = schemaObj.properties[property]; | ||
| } | ||
| }); | ||
| } | ||
| if (schemaObj.allOf) { | ||
| schemaObj.allOf.forEach((parent) => { | ||
| collectProperties(parent, props); | ||
| }); | ||
| } | ||
| } | ||
| if (schema.required && Array.isArray(schema.required)) { | ||
| const props = {}; | ||
| collectProperties(schema, props); | ||
| schema.required.forEach((requiredProperty) => { | ||
| if (!props[requiredProperty]) { | ||
| this.reportError( | ||
| `Property \`${requiredProperty}\` is listed as required but does not exist in \`${schemaId}\`.` | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Checks the given parameter list for duplicates. | ||
| * | ||
| */ | ||
| checkForDuplicates(params, schemaId) { | ||
| for (let i = 0; i < params.length - 1; i++) { | ||
| const outer = params[i]; | ||
| for (let j = i + 1; j < params.length; j++) { | ||
| const inner = params[j]; | ||
| if (outer.name === inner.name && outer.in === inner.in) { | ||
| this.reportError(`Found multiple \`${outer.in}\` parameters named \`${outer.name}\` in \`${schemaId}\`.`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| // src/validators/spec.ts | ||
| function validateSpec(api, rules) { | ||
| let validator; | ||
| const specificationName = getSpecificationName(api); | ||
| if (isOpenAPI(api)) { | ||
| validator = new OpenAPISpecificationValidator(api, rules.openapi); | ||
| } else { | ||
| validator = new SwaggerSpecificationValidator(api); | ||
| } | ||
| validator.run(); | ||
| if (!validator.errors.length) { | ||
| return { | ||
| valid: true, | ||
| warnings: validator.warnings, | ||
| specification: specificationName | ||
| }; | ||
| } | ||
| return { | ||
| valid: false, | ||
| errors: validator.errors, | ||
| warnings: validator.warnings, | ||
| additionalErrors: 0, | ||
| specification: specificationName | ||
| }; | ||
| } | ||
| // src/index.ts | ||
| async function parse(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| const parser = new $RefParser(); | ||
| const schema = await parser.parse(args.path, args.schema, parserOptions); | ||
| repairSchema(schema, args.path); | ||
| return schema; | ||
| } | ||
| async function bundle(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| const parser = new $RefParser(); | ||
| await parser.bundle(args.path, args.schema, parserOptions); | ||
| repairSchema(parser.schema, args.path); | ||
| return parser.schema; | ||
| } | ||
| async function dereference(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| const parser = new $RefParser(); | ||
| await parser.dereference(args.path, args.schema, parserOptions); | ||
| repairSchema(parser.schema, args.path); | ||
| return parser.schema; | ||
| } | ||
| async function validate(api, options) { | ||
| const args = normalizeArguments(api); | ||
| const parserOptions = convertOptionsForParser(options); | ||
| let result; | ||
| const circular$RefOption = parserOptions.dereference.circular; | ||
| parserOptions.dereference.circular = "ignore"; | ||
| const parser = new $RefParser(); | ||
| try { | ||
| await parser.dereference(args.path, args.schema, parserOptions); | ||
| } catch (err) { | ||
| if (err instanceof MissingPointerError) { | ||
| return { | ||
| valid: false, | ||
| errors: [{ message: err.message }], | ||
| warnings: [], | ||
| additionalErrors: 0, | ||
| specification: null | ||
| }; | ||
| } | ||
| throw err; | ||
| } | ||
| if (!isSwagger(parser.schema) && !isOpenAPI(parser.schema)) { | ||
| return { | ||
| valid: false, | ||
| errors: [{ message: "Supplied schema is not a valid API definition." }], | ||
| warnings: [], | ||
| additionalErrors: 0, | ||
| specification: null | ||
| }; | ||
| } | ||
| parserOptions.dereference.circular = circular$RefOption; | ||
| result = validateSchema(parser.schema, options); | ||
| if (!result.valid) { | ||
| return result; | ||
| } | ||
| if (parser.$refs?.circular) { | ||
| if (circular$RefOption === true) { | ||
| dereferenceInternal(parser, parserOptions); | ||
| } else if (circular$RefOption === false) { | ||
| throw new ReferenceError( | ||
| "The API contains circular references but the validator is configured to not permit them." | ||
| ); | ||
| } | ||
| } | ||
| const rules = options?.validate?.rules?.openapi; | ||
| result = validateSpec(parser.schema, { | ||
| openapi: { | ||
| "array-without-items": rules?.["array-without-items"] || "error", | ||
| "duplicate-non-request-body-parameters": rules?.["duplicate-non-request-body-parameters"] || "error", | ||
| "duplicate-operation-id": rules?.["duplicate-operation-id"] || "error", | ||
| "non-optional-path-parameters": rules?.["non-optional-path-parameters"] || "error", | ||
| "path-parameters-not-in-parameters": rules?.["path-parameters-not-in-parameters"] || "error", | ||
| "path-parameters-not-in-path": rules?.["path-parameters-not-in-path"] || "error" | ||
| } | ||
| }); | ||
| return result; | ||
| } | ||
| function compileErrors(result) { | ||
| const specName = result.specification || "API definition"; | ||
| const status = !result.valid ? "failed" : "succeeded, but with warnings"; | ||
| const message = [`${specName} schema validation ${status}.`]; | ||
| if (result.valid === false) { | ||
| if (result.errors.length) { | ||
| message.push(...result.errors.map((err) => err.message)); | ||
| } | ||
| } | ||
| if (result.warnings.length) { | ||
| if (result.valid === false && result.errors.length) { | ||
| message.push("We have also found some additional warnings:"); | ||
| } | ||
| message.push(...result.warnings.map((warn) => warn.message)); | ||
| } | ||
| if (result.valid === false && result.additionalErrors > 0) { | ||
| message.push( | ||
| `Plus an additional ${result.additionalErrors} errors. Please resolve the above and re-run validation to see more.` | ||
| ); | ||
| } | ||
| return message.join("\n\n"); | ||
| } | ||
| export { | ||
| bundle, | ||
| compileErrors, | ||
| dereference, | ||
| parse, | ||
| validate | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
Sorry, the diff of this file is too big to display
+5
-7
@@ -1,5 +0,3 @@ | ||
| The MIT License (MIT) | ||
| Copyright © 2025 ReadMe | ||
| Copyright (c) 2015 James Messinger | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
@@ -12,4 +10,4 @@ of this software and associated documentation files (the "Software"), to deal | ||
| The above copyright notice and this permission notice shall be included in | ||
| all copies or substantial portions of the Software. | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
@@ -21,3 +19,3 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| THE SOFTWARE. | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+42
-34
| { | ||
| "name": "@readme/openapi-parser", | ||
| "version": "2.7.0", | ||
| "version": "3.0.0", | ||
| "description": "Swagger 2.0 and OpenAPI 3.x parser and validator for Node and browsers", | ||
| "license": "MIT", | ||
| "author": "ReadMe <support@readme.io> (https://readme.com)", | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "exports": { | ||
| ".": { | ||
| "require": "./dist/index.cjs", | ||
| "import": "./dist/index.js" | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "main": "dist/index.cjs", | ||
| "module": "dist/index.js", | ||
| "types": "dist/index.d.cts", | ||
| "engines": { | ||
| "node": ">=20" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "keywords": [ | ||
@@ -22,37 +42,27 @@ "swagger", | ||
| ], | ||
| "author": { | ||
| "name": "James Messinger", | ||
| "url": "https://jamesmessinger.com" | ||
| }, | ||
| "homepage": "https://apitools.dev/swagger-parser/", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/readmeio/openapi-parser.git" | ||
| "url": "git://github.com/readmeio/oas.git", | ||
| "directory": "packages/parser" | ||
| }, | ||
| "license": "MIT", | ||
| "main": "lib/index.js", | ||
| "typings": "lib/index.d.ts", | ||
| "files": [ | ||
| "lib" | ||
| ], | ||
| "engines": { | ||
| "node": ">=18" | ||
| "bugs": { | ||
| "url": "https://github.com/readmeio/oas/issues" | ||
| }, | ||
| "scripts": { | ||
| "lint": "npm run lint:js && npm run prettier", | ||
| "lint:js": "eslint . --ext .js,.ts --ignore-path .gitignore", | ||
| "pretest": "npm run lint", | ||
| "prettier": "prettier --check .", | ||
| "prettier:write": "prettier --check --write .", | ||
| "test": "vitest run --coverage" | ||
| "attw": "attw --pack --format ascii --profile node16", | ||
| "build": "tsup", | ||
| "lint": "npm run lint:types && npm run lint:js", | ||
| "lint:js": "eslint . --ext .js,.ts --ignore-path ../../.gitignore", | ||
| "lint:types": "tsc --noEmit", | ||
| "prebuild": "rm -rf dist/", | ||
| "prepack": "npm run build", | ||
| "test": "echo 'Please run tests from the root!' && exit 1" | ||
| }, | ||
| "dependencies": { | ||
| "@apidevtools/swagger-methods": "^3.0.2", | ||
| "@jsdevtools/ono": "^7.1.3", | ||
| "@readme/better-ajv-errors": "^2.0.0", | ||
| "@readme/json-schema-ref-parser": "^1.2.0", | ||
| "@apidevtools/json-schema-ref-parser": "^11.9.2", | ||
| "@readme/better-ajv-errors": "^2.3.2", | ||
| "@readme/openapi-schemas": "^3.1.0", | ||
| "@types/json-schema": "^7.0.15", | ||
| "ajv": "^8.12.0", | ||
| "ajv-draft-04": "^1.0.0", | ||
| "call-me-maybe": "^1.0.1" | ||
| "ajv-draft-04": "^1.0.0" | ||
| }, | ||
@@ -63,13 +73,11 @@ "peerDependencies": { | ||
| "devDependencies": { | ||
| "@readme/eslint-config": "^14.1.2", | ||
| "@types/node": "^22.10.10", | ||
| "@vitest/coverage-v8": "^3.0.4", | ||
| "@types/node": "^22.13.1", | ||
| "@vitest/expect": "^3.0.7", | ||
| "eslint": "^8.56.0", | ||
| "openapi-types": "^12.1.3", | ||
| "prettier": "^3.1.1", | ||
| "sinon": "^19.0.2", | ||
| "typescript": "^5.7.3", | ||
| "vitest": "^3.0.4" | ||
| "vitest": "^3.0.5" | ||
| }, | ||
| "prettier": "@readme/eslint-config/prettier" | ||
| "prettier": "@readme/eslint-config/prettier", | ||
| "gitHead": "9794d956c5e51149abb030afc94a61d140b5fa3a" | ||
| } |
+189
-51
@@ -1,42 +0,57 @@ | ||
| # Swagger 2.0 and OpenAPI 3.x parser/validator | ||
| <p align="center"> | ||
| <a href="https://npm.im/@readme/openapi-parser"> | ||
| <img src="https://raw.githubusercontent.com/readmeio/oas/main/.github/tooling-hero.png" alt="@readme/openapi-parser" /> | ||
| </a> | ||
| </p> | ||
| [](https://github.com/readmeio/openapi-parser/actions) | ||
| [](https://apis.guru/browse-apis/) | ||
| <p align="center"> | ||
| A Swagger 2.0 and OpenAPI 3.x validation and parsing engine | ||
| </p> | ||
| [](https://www.npmjs.com/package/@readme/openapi-parser) | ||
| [](LICENSE) | ||
| <p align="center"> | ||
| <a href="https://npm.im/@readme/openapi-parser"><img src="https://img.shields.io/npm/v/@readme/openapi-parser?style=for-the-badge" alt="NPM Version"></a> | ||
| <a href="https://npm.im/@readme/openapi-parser"><img src="https://img.shields.io/node/v/@readme/openapi-parser?style=for-the-badge" alt="Node Version"></a> | ||
| <a href="https://npm.im/@readme/openapi-parser"><img src="https://img.shields.io/npm/l/@readme/openapi-parser?style=for-the-badge" alt="MIT License"></a> | ||
| <a href="https://github.com/readmeio/oas/tree/main/packages/parser"><img src="https://img.shields.io/github/actions/workflow/status/readmeio/oas/ci.yml?branch=main&style=for-the-badge" alt="Build status"></a> | ||
| </p> | ||
| [](https://github.com/readmeio/openapi-parser/actions) | ||
| <p align="center"> | ||
| <a href="https://readme.com"><img src="https://raw.githubusercontent.com/readmeio/.github/main/oss-badge.svg" /></a> | ||
| </p> | ||
| [](https://apitools.dev/swagger-parser/online/) | ||
| `@readme/openapi-parser` is a library to validate and parse [OpenAPI](https://openapis.org) and Swagger API definitions. It is a hard fork of [@apidevtools/swagger-parser](https://npm.im/@apidevtools/swagger-parser) and offers support for improved validation error messages as well as error leveling. | ||
| ## Features | ||
| --- | ||
| - Parses Swagger specs in **JSON** or **YAML** format | ||
| - Validates against the [Swagger 2.0 schema](https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v2.0/schema.json), [OpenAPI 3.0 Schema](https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.0/schema.json), or [OpenAPI 3.1 Schema](https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.1/schema.json) | ||
| - [Resolves](https://apitools.dev/swagger-parser/docs/swagger-parser.html#resolveapi-options-callback) all `$ref` pointers, including external files and URLs | ||
| - Can [bundle](https://apitools.dev/swagger-parser/docs/swagger-parser.html#bundleapi-options-callback) all your Swagger files into a single file that only has _internal_ `$ref` pointers | ||
| - Can [dereference](https://apitools.dev/swagger-parser/docs/swagger-parser.html#dereferenceapi-options-callback) all `$ref` pointers, giving you a normal JavaScript object that's easy to work with | ||
| - **[Tested](https://github.com/readmeio/openapi-parser/actions)** in Node.js and all modern web browsers on Mac, Windows, and Linux | ||
| - Tested on **[over 1,500 real-world APIs](https://apis.guru/browse-apis/)** from Google, Microsoft, Facebook, Spotify, etc. | ||
| - Supports [circular references](https://apitools.dev/swagger-parser/docs/#circular-refs), nested references, back-references, and cross-references | ||
| - Maintains object reference equality — `$ref` pointers to the same value always resolve to the same object instance | ||
| - [Installation](#installation) | ||
| - [Features](#features) | ||
| - [Usage](#usage) | ||
| - [validate()](#validate) | ||
| - [dereference()](#dereference) | ||
| - [bundle()](#bundle) | ||
| - [parse()](#parse) | ||
| - [Error Handling](#error-handling) | ||
| ## Example | ||
| ## Installation | ||
| ```javascript | ||
| OpenAPIParser.validate(myAPI, (err, api) => { | ||
| if (err) { | ||
| console.error(err); | ||
| } else { | ||
| console.log('API name: %s, Version: %s', api.info.title, api.info.version); | ||
| } | ||
| }); | ||
| ``` | ||
| npm install @readme/openapi-parser | ||
| ``` | ||
| Or use `async`/`await` or [Promise](http://javascriptplayground.com/blog/2015/02/promises/) syntax instead. The following example is the same as above: | ||
| ## Features | ||
| ```javascript | ||
| - Parses API definitions in either JSON or YAML formats. | ||
| - Validates against the [Swagger 2.0](https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v2.0), [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.0), or [OpenAPI 3.1](https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.1) specifications. | ||
| - Resolves all `$ref` pointers, including external files and URLs. | ||
| - Can bundle all of your referenced API definitions into a single file that only has _internal_ `$ref` pointers. | ||
| - Can dereference all `$ref` pointers, giving you a normal JSON object that's easy to work with. | ||
| - Works in all modern browsers! | ||
| ## Usage | ||
| ```ts | ||
| import { validate } from '@readme/openapi-parser'; | ||
| try { | ||
| let api = await OpenAPIParser.validate(myAPI); | ||
| const api = await validate(petstore); | ||
| console.log('API name: %s, Version: %s', api.info.title, api.info.version); | ||
@@ -48,46 +63,169 @@ } catch (err) { | ||
| For more detailed examples, please see the [API Documentation](https://apitools.dev/swagger-parser/docs/) | ||
| ### `validate()` | ||
| ## Installation | ||
| Validates the API definition against the [Swagger 2.0](https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v2.0), [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.0), or [OpenAPI 3.1](https://github.com/OAI/OpenAPI-Specification/tree/main/schemas/v3.1) specifications. | ||
| Install using [npm](https://docs.npmjs.com/about-npm/): | ||
| In addition to validating the API definition against their respective specification schemas, it will also be validated against specific areas that aren't covered by the Swagger or OpenAPI schemas, such as duplicate parameters, invalid component schema names, or duplicate `operationId` values. | ||
| ```bash | ||
| npm install @readme/openapi-parser | ||
| If validation fails an error will be thrown with information about what, and where, the error lies within the API definition. | ||
| Internally this method invokes [`dereference()`](#dereference) so the returned object, whether it's a Swagger or OpenAPI definition, will be fully dereferenced. | ||
| ```ts | ||
| import { validate } from '@readme/openapi-parser'; | ||
| const result = await validate(petstore); | ||
| if (result.valid) { | ||
| console.log('🍭 The API is valid!'); | ||
| } else { | ||
| console.error(result.errors); | ||
| } | ||
| ``` | ||
| ## Usage | ||
| <details> | ||
| <summary>Error output example</summary> | ||
| When using Swagger Parser in Node.js apps, you'll probably want to use **CommonJS** syntax: | ||
| ``` | ||
| [ | ||
| { | ||
| message: 'REQUIRED must have required property 'url' | ||
| ```javascript | ||
| const OpenAPIParser = require('@readme/openapi-parser'); | ||
| 7 | }, | ||
| 8 | "servers": [ | ||
| > 9 | { | ||
| | ^ url is missing here! | ||
| 10 | "urll": "http://petstore.swagger.io/v2" | ||
| 11 | } | ||
| 12 | ],' | ||
| } | ||
| ] | ||
| ``` | ||
| When using a transpiler such as [Babel](https://babeljs.io/) or [TypeScript](https://www.typescriptlang.org/), or a bundler such as [Webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/), you can use **ECMAScript modules** syntax instead: | ||
| </details> | ||
| ```javascript | ||
| import OpenAPIParser from '@readme/openapi-parser'; | ||
| #### Human-readable errors | ||
| By default, `validate` returns a `ValidationResult` which will contain an array of errors. If you would like to convert this shape into a human-readable error string, you can do so by utilizing our `compileErrors` utility: | ||
| ```ts | ||
| import { validate, compileErrors } from '@readme/openapi-parser'; | ||
| const result = await validate(petstore); | ||
| if (result.valid) { | ||
| console.log('🍭 The API is valid!'); | ||
| } else { | ||
| console.error(compileErrors(result)); | ||
| } | ||
| ``` | ||
| ## Differences from `@apidevtools/swagger-parser` | ||
| ``` | ||
| OpenAPI schema validation failed. | ||
| `@apidevtools/swagger-parser` returns schema validation errors as the raw error stack from Ajv. For example: | ||
| REQUIRED must have required property 'url' | ||
| <img src="https://user-images.githubusercontent.com/33762/137796620-cd7de717-6492-4cff-b291-8629ed5dcd6e.png" width="600" /> | ||
| 7 | }, | ||
| 8 | "servers": [ | ||
| > 9 | { | ||
| | ^ url is missing here! | ||
| 10 | "urll": "http://petstore.swagger.io/v2" | ||
| 11 | } | ||
| 12 | ], */ | ||
| ``` | ||
| To reduce the amount of potentially unnecessary noise that these JSON pointer errors provide, `@readme/openapi-parser` utilizes [better-ajv-errors](https://www.npmjs.com/package/@readme/better-ajv-errors), along with some intelligent reduction logic, to only surface the errors that _actually_ matter. | ||
| `compileErrors` can also be used to turn validation warnings into a human-readable string. | ||
| #### Warnings | ||
| This library supports downgrading certain specification-level checks, that would be normally classified as a validation error, to a general warning. To configure these you do so by supplying the `validate()` call your config: | ||
| ```ts | ||
| import { validate, compileErrors } from '@readme/openapi-parser'; | ||
| const result = await validate(petstore, { | ||
| validate: { | ||
| rules: { | ||
| openapi: { | ||
| 'path-parameters-not-in-path': 'warning', | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| if (result.valid) { | ||
| if (result.warnings.length) { | ||
| console.warn('🚸 The API is valid but has some warnings.'); | ||
| console.warn(result.warnings); | ||
| } else { | ||
| console.log('🍭 The API is valid!'); | ||
| } | ||
| } else { | ||
| console.error(compileErrors(result)); | ||
| } | ||
| ``` | ||
| The following OpenAPI rules can be downgraded to warnings. By default, they are all treated as errors. We do not support downgrading any Swagger specification errors to warnings -- only OpenAPI. | ||
| <!-- prettier-ignore-start --> | ||
| | Rule | What it validates | | ||
| | :--- | :--- | | ||
| | `array-without-items` | Schemas that are defined as `type: array` must also have an `items` schema. | | ||
| | `duplicate-non-request-body-parameters` | Parameters must be unique. | | ||
| | `duplicate-operation-id` | The `operationId` definition in a path object must be unique. | | ||
| | `non-optional-path-parameters` | Parameters that are defined within the path URI must be specified as being `required`. | | ||
| | `path-parameters-not-in-parameters` | Path parameters defined in a path URI path template must also be specified as part of that paths `parameters`. | | ||
| | `path-parameters-not-in-path` | Path parameters defined in `parameters` must also be specified in the path URI with path templating. | | ||
| <!-- prettier-ignore-end --> | ||
| #### Colorizing errors | ||
| This library supports colorizing errors with the [picocolors](https://npm.im/picocolors) library. To enable it, supply the `validation.errors.colorize` option. The default behavior is `false`. | ||
| ```ts | ||
| const result = await validate(petstore, { | ||
| validate: { | ||
| errors: { | ||
| colorize: true, | ||
| }, | ||
| }, | ||
| }); | ||
| ``` | ||
| <img src="https://user-images.githubusercontent.com/33762/137796648-7e1157c2-cee4-466e-9129-dd2a743dd163.png" width="600" /> | ||
| Additionally with these error reporting differences, this library ships with a `validation.colorizeErrors` option that will disable colorization within these prettified errors. | ||
| ### `.dereference()` | ||
| ## Browser support | ||
| Dereferences all `$ref` pointers in the supplied API definition, replacing each reference with its resolved value. This results in an API definition that does not contain _any_ `$ref` pointers. Instead, it's a normal JSON object tree that can easily be crawled and used just like any other object. This is great for programmatic usage, especially when using tools that don't understand JSON references. | ||
| Swagger Parser supports recent versions of every major web browser. Older browsers may require [Babel](https://babeljs.io/) and/or [polyfills](https://babeljs.io/docs/en/next/babel-polyfill). | ||
| ```ts | ||
| import { dereference } from '@readme/openapi-parser'; | ||
| To use Swagger Parser in a browser, you'll need to use a bundling tool such as [Webpack](https://webpack.js.org/), [Rollup](https://rollupjs.org/), [Parcel](https://parceljs.org/), or [Browserify](http://browserify.org/). Some bundlers may require a bit of configuration, such as setting `browser: true` in [rollup-plugin-resolve](https://github.com/rollup/rollup-plugin-node-resolve). | ||
| const api = await dereference(petstore); | ||
| ## API Documentation | ||
| // The `api` object is a normal JSON object so you can access any part of the | ||
| // API definition using object notation. | ||
| console.log(api.components.schemas.pet.properties.name); // => { type: "string" } | ||
| ``` | ||
| Full API documentation is available [right here](https://apitools.dev/swagger-parser/docs/) | ||
| ### `.bundle()` | ||
| Bundles all referenced files and URLs into a single API definition that only has _internal_ `$ref` pointers. This lets you split up your definition however you want while you're building it, but later combine all those files together when it's time to package or distribute the API definition to other people. The resulting definition size will be small, since it will still contain _internal_ JSON references rather than being fully-dereferenced. | ||
| ```ts | ||
| import { bundle } from '@readme/openapi-parser'; | ||
| const api = await bundle(myAPI); | ||
| console.log(api.components.schemas.pet); // => { $ref: "#/components/schemas~1pet.yaml" } | ||
| ``` | ||
| ### `.parse()` | ||
| Parses the given API definition, in JSON or YAML format, and returns it as a JSON object. This method **does not** resolve `$ref` pointers or dereference anything. It simply parses _one_ file and returns it. | ||
| ```ts | ||
| import { parse } from '@readme/openapi-parser'; | ||
| const api = await parse(myAPI); | ||
| console.log('API name: %s, Version: %s', api.info.title, api.info.version); | ||
| ``` |
-563
| import { OpenAPI } from 'openapi-types'; | ||
| export = OpenAPIParser; | ||
| /** | ||
| * This is the default export of Swagger Parser. You can creates instances of this class using new OpenAPIParser(), or you can just call its static methods. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html | ||
| */ | ||
| declare class OpenAPIParser { | ||
| /** | ||
| * The `api` property is the parsed/bundled/dereferenced OpenAPI definition. This is the same value that is passed to the callback function (or Promise) when calling the parse, bundle, or dereference methods. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#api | ||
| */ | ||
| public api: OpenAPI.Document; | ||
| /** | ||
| * The $refs property is a `$Refs` object, which lets you access all of the externally-referenced files in the OpenAPI definition, as well as easily get and set specific values in the OpenAPI definition using JSON pointers. | ||
| * | ||
| * This is the same value that is passed to the callback function (or Promise) when calling the `resolve` method. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#refs | ||
| */ | ||
| public $refs: OpenAPIParser.$Refs; | ||
| /** | ||
| * Parses, dereferences, and validates the given Swagger API. | ||
| * Depending on the options, validation can include JSON Schema validation and/or Swagger Spec validation. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#validateapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the dereferenced OpenAPI definition | ||
| */ | ||
| public validate(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public validate( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public validate( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public validate(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public validate(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public validate( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * Parses, dereferences, and validates the given Swagger API. | ||
| * Depending on the options, validation can include JSON Schema validation and/or Swagger Spec validation. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#validateapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the dereferenced OpenAPI definition | ||
| */ | ||
| public static validate(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public static validate( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static validate( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static validate(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public static validate(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public static validate( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * Dereferences all `$ref` pointers in the OpenAPI definition, replacing each reference with its resolved value. This results in an API definition that does not contain any `$ref` pointers. Instead, it's a normal JavaScript object tree that can easily be crawled and used just like any other JavaScript object. This is great for programmatic usage, especially when using tools that don't understand JSON references. | ||
| * | ||
| * The dereference method maintains object reference equality, meaning that all `$ref` pointers that point to the same object will be replaced with references to the same object. Again, this is great for programmatic usage, but it does introduce the risk of circular references, so be careful if you intend to serialize the API definition using `JSON.stringify()`. Consider using the bundle method instead, which does not create circular references. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#dereferenceapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the dereferenced OpenAPI definition | ||
| */ | ||
| public dereference(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public dereference( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public dereference( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public dereference(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public dereference(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public dereference( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * Dereferences all `$ref` pointers in the OpenAPI definition, replacing each reference with its resolved value. This results in an API definition that does not contain any `$ref` pointers. Instead, it's a normal JavaScript object tree that can easily be crawled and used just like any other JavaScript object. This is great for programmatic usage, especially when using tools that don't understand JSON references. | ||
| * | ||
| * The dereference method maintains object reference equality, meaning that all `$ref` pointers that point to the same object will be replaced with references to the same object. Again, this is great for programmatic usage, but it does introduce the risk of circular references, so be careful if you intend to serialize the API definition using `JSON.stringify()`. Consider using the bundle method instead, which does not create circular references. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#dereferenceapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the dereferenced OpenAPI definition | ||
| */ | ||
| public static dereference(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public static dereference( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static dereference( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static dereference(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public static dereference(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public static dereference( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * Bundles all referenced files/URLs into a single API definition that only has internal `$ref` pointers. This lets you split-up your API definition however you want while you're building it, but easily combine all those files together when it's time to package or distribute the API definition to other people. The resulting API definition size will be small, since it will still contain internal JSON references rather than being fully-dereferenced. | ||
| * | ||
| * This also eliminates the risk of circular references, so the API definition can be safely serialized using `JSON.stringify()`. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#bundleapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the bundled API definition object | ||
| */ | ||
| public bundle(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public bundle( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public bundle( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public bundle(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public bundle(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public bundle( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * Bundles all referenced files/URLs into a single API definition that only has internal `$ref` pointers. This lets you split-up your API definition however you want while you're building it, but easily combine all those files together when it's time to package or distribute the API definition to other people. The resulting API definition size will be small, since it will still contain internal JSON references rather than being fully-dereferenced. | ||
| * | ||
| * This also eliminates the risk of circular references, so the API definition can be safely serialized using `JSON.stringify()`. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#bundleapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the bundled API definition object | ||
| */ | ||
| public static bundle(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public static bundle( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static bundle( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static bundle(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public static bundle(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public static bundle( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * *This method is used internally by other methods, such as `bundle` and `dereference`. You probably won't need to call this method yourself.* | ||
| * | ||
| * Parses the given OpenAPI definition file (in JSON or YAML format), and returns it as a JavaScript object. This method `does not` resolve `$ref` pointers or dereference anything. It simply parses one file and returns it. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#parseapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. The path can be absolute or relative. In Node, the path is relative to `process.cwd()`. In the browser, it's relative to the URL of the page. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the parsed OpenAPI definition object, or an error | ||
| */ | ||
| public parse(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public parse( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public parse( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public parse(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public parse(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public parse( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * *This method is used internally by other methods, such as `bundle` and `dereference`. You probably won't need to call this method yourself.* | ||
| * | ||
| * Parses the given OpenAPI definition file (in JSON or YAML format), and returns it as a JavaScript object. This method `does not` resolve `$ref` pointers or dereference anything. It simply parses one file and returns it. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#parseapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. The path can be absolute or relative. In Node, the path is relative to `process.cwd()`. In the browser, it's relative to the URL of the page. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive the parsed OpenAPI definition object, or an error | ||
| */ | ||
| public static parse(api: string | OpenAPI.Document, callback: OpenAPIParser.ApiCallback): void; | ||
| public static parse( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static parse( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.ApiCallback, | ||
| ): void; | ||
| public static parse(api: string | OpenAPI.Document): Promise<OpenAPI.Document>; | ||
| public static parse(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPI.Document>; | ||
| public static parse( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPI.Document>; | ||
| /** | ||
| * *This method is used internally by other methods, such as `bundle` and `dereference`. You probably won't need to call this method yourself.* | ||
| * | ||
| * Resolves all JSON references (`$ref` pointers) in the given OpenAPI definition file. If it references any other files/URLs, then they will be downloaded and resolved as well. This method **does not** dereference anything. It simply gives you a `$Refs` object, which is a map of all the resolved references and their values. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#resolveapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive a `$Refs` object | ||
| */ | ||
| public resolve(api: string | OpenAPI.Document, callback: OpenAPIParser.$RefsCallback): void; | ||
| public resolve( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.$RefsCallback, | ||
| ): void; | ||
| public resolve( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.$RefsCallback, | ||
| ): void; | ||
| public resolve(api: string | OpenAPI.Document): Promise<OpenAPIParser.$Refs>; | ||
| public resolve(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPIParser.$Refs>; | ||
| public resolve( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPIParser.$Refs>; | ||
| /** | ||
| * *This method is used internally by other methods, such as `bundle` and `dereference`. You probably won't need to call this method yourself.* | ||
| * | ||
| * Resolves all JSON references (`$ref` pointers) in the given OpenAPI definition file. If it references any other files/URLs, then they will be downloaded and resolved as well. This method **does not** dereference anything. It simply gives you a `$Refs` object, which is a map of all the resolved references and their values. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/swagger-parser.html#resolveapi-options-callback | ||
| * | ||
| * @param api An OpenAPI definition, or the file path or URL of an OpenAPI definition. See the `parse` method for more info. | ||
| * @param options (optional) | ||
| * @param callback (optional) A callback that will receive a `$Refs` object | ||
| */ | ||
| public static resolve(api: string | OpenAPI.Document, callback: OpenAPIParser.$RefsCallback): void; | ||
| public static resolve( | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.$RefsCallback, | ||
| ): void; | ||
| public static resolve( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| callback: OpenAPIParser.$RefsCallback, | ||
| ): void; | ||
| public static resolve(api: string | OpenAPI.Document): Promise<OpenAPIParser.$Refs>; | ||
| public static resolve(api: string | OpenAPI.Document, options: OpenAPIParser.Options): Promise<OpenAPIParser.$Refs>; | ||
| public static resolve( | ||
| baseUrl: string, | ||
| api: string | OpenAPI.Document, | ||
| options: OpenAPIParser.Options, | ||
| ): Promise<OpenAPIParser.$Refs>; | ||
| } | ||
| // eslint-disable-next-line no-redeclare | ||
| declare namespace OpenAPIParser { | ||
| export type ApiCallback = (err: Error | null, api?: OpenAPI.Document) => any; | ||
| export type $RefsCallback = (err: Error | null, $refs?: $Refs) => any; | ||
| /** | ||
| * See https://apitools.dev/swagger-parser/docs/options.html | ||
| */ | ||
| export interface Options { | ||
| /** | ||
| * The `parse` options determine how different types of files will be parsed. | ||
| * | ||
| * JSON Schema `$Ref` Parser comes with built-in JSON, YAML, plain-text, and binary parsers, any of which you can configure or disable. You can also add your own custom parsers if you want. | ||
| */ | ||
| parse?: { | ||
| json?: ParserOptions | boolean; | ||
| yaml?: ParserOptions | boolean; | ||
| text?: (ParserOptions & { encoding?: string }) | boolean; | ||
| [key: string]: ParserOptions | boolean | undefined; | ||
| }; | ||
| /** | ||
| * The `resolve` options control how Swagger Parser will resolve file paths and URLs, and how those files will be read/downloaded. | ||
| * | ||
| * JSON Schema `$Ref` Parser comes with built-in support for HTTP and HTTPS, as well as support for local files (when running in Node.js). You can configure or disable either of these built-in resolvers. You can also add your own custom resolvers if you want. | ||
| */ | ||
| resolve?: { | ||
| /** | ||
| * Determines whether external $ref pointers will be resolved. If this option is disabled, then external `$ref` pointers will simply be ignored. | ||
| */ | ||
| external?: boolean; | ||
| file?: Partial<ResolverOptions> | boolean; | ||
| http?: HTTPResolverOptions | boolean; | ||
| }; | ||
| /** | ||
| * The `dereference` options control how JSON Schema `$Ref` Parser will dereference `$ref` pointers within the JSON schema. | ||
| */ | ||
| dereference?: { | ||
| /** | ||
| * Determines whether circular `$ref` pointers are handled. | ||
| * | ||
| * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any circular references. | ||
| * | ||
| * If set to `"ignore"`, then circular references will simply be ignored. No error will be thrown, but the `$Refs.circular` property will still be set to `true`. | ||
| */ | ||
| circular?: boolean | 'ignore'; | ||
| }; | ||
| /** | ||
| * The `validate` options control how Swagger Parser will validate the API. | ||
| */ | ||
| validate?: { | ||
| /** | ||
| * If set to `true` then validation errors will be colorized and styled. | ||
| */ | ||
| colorizeErrors?: boolean | false; | ||
| /** | ||
| * If set to `false`, then validating against the Swagger 2.0 Schema, OpenAPI 3.0 Schema, or OpenAPI 3.1 Schema is disabled. | ||
| */ | ||
| schema?: boolean; | ||
| /** | ||
| * If set to `false`, then validating against the Swagger 2.0 Specification is disabled. | ||
| */ | ||
| spec?: boolean; | ||
| }; | ||
| } | ||
| export interface HTTPResolverOptions extends Partial<ResolverOptions> { | ||
| /** | ||
| * You can specify any HTTP headers that should be sent when downloading files. For example, some servers may require you to set the `Accept` or `Referrer` header. | ||
| */ | ||
| headers?: object; | ||
| /** | ||
| * The amount of time (in milliseconds) to wait for a response from the server when downloading files. The default is 5 seconds. | ||
| */ | ||
| timeout?: number; | ||
| /** | ||
| * The maximum number of HTTP redirects to follow per file. The default is 5. To disable automatic following of redirects, set this to zero. | ||
| */ | ||
| redirects?: number; | ||
| /** | ||
| * Set this to `true` if you're downloading files from a CORS-enabled server that requires authentication | ||
| */ | ||
| withCredentials?: boolean; | ||
| } | ||
| /** | ||
| * JSON Schema `$Ref` Parser comes with built-in resolvers for HTTP and HTTPS URLs, as well as local filesystem paths (when running in Node.js). You can add your own custom resolvers to support additional protocols, or even replace any of the built-in resolvers with your own custom implementation. | ||
| * | ||
| * See https://apitools.dev/json-schema-ref-parser/docs/plugins/resolvers.html | ||
| */ | ||
| export interface ResolverOptions { | ||
| /** | ||
| * All resolvers have an order property, even the built-in resolvers. If you don't specify an order property, then your resolver will run last. Specifying `order: 1`, like we did in this example, will make your resolver run first. Or you can squeeze your resolver in-between some of the built-in resolvers. For example, `order: 101` would make it run after the file resolver, but before the HTTP resolver. You can see the order of all the built-in resolvers by looking at their source code. | ||
| * | ||
| * The order property and canRead property are related to each other. For each file that Swagger Parser needs to resolve, it first determines which resolvers can read that file by checking their canRead property. If only one resolver matches a file, then only that one resolver is called, regardless of its order. If multiple resolvers match a file, then those resolvers are tried in order until one of them successfully reads the file. Once a resolver successfully reads the file, the rest of the resolvers are skipped. | ||
| */ | ||
| order?: number; | ||
| /** | ||
| * The `canRead` property tells JSON Schema `$Ref` Parser what kind of files your resolver can read. In this example, we've simply specified a regular expression that matches "mogodb://" URLs, but we could have used a simple boolean, or even a function with custom logic to determine which files to resolve. Here are examples of each approach: | ||
| */ | ||
| canRead: boolean | RegExp | string | string[] | ((file: FileInfo) => boolean); | ||
| /** | ||
| * This is where the real work of a resolver happens. The `read` method accepts the same file info object as the `canRead` function, but rather than returning a boolean value, the `read` method should return the contents of the file. The file contents should be returned in as raw a form as possible, such as a string or a byte array. Any further parsing or processing should be done by parsers. | ||
| * | ||
| * Unlike the `canRead` function, the `read` method can also be asynchronous. This might be important if your resolver needs to read data from a database or some other external source. You can return your asynchronous value using either an ES6 Promise or a Node.js-style error-first callback. Of course, if your resolver has the ability to return its data synchronously, then that's fine too. Here are examples of all three approaches: | ||
| */ | ||
| read( | ||
| file: FileInfo, | ||
| callback?: (error: Error | null, data: string | null) => any, | ||
| ): string | Buffer | Promise<string | Buffer>; | ||
| } | ||
| export interface ParserOptions { | ||
| /** | ||
| * Parsers run in a specific order, relative to other parsers. For example, a parser with `order: 5` will run before a parser with `order: 10`. If a parser is unable to successfully parse a file, then the next parser is tried, until one succeeds or they all fail. | ||
| * | ||
| * You can change the order in which parsers run, which is useful if you know that most of your referenced files will be a certain type, or if you add your own custom parser that you want to run first. | ||
| */ | ||
| order?: number; | ||
| /** | ||
| * All of the built-in parsers allow empty files by default. The JSON and YAML parsers will parse empty files as `undefined`. The text parser will parse empty files as an empty string. The binary parser will parse empty files as an empty byte array. | ||
| * | ||
| * You can set `allowEmpty: false` on any parser, which will cause an error to be thrown if a file empty. | ||
| */ | ||
| allowEmpty?: boolean; | ||
| /** | ||
| * Determines which parsers will be used for which files. | ||
| * | ||
| * A regular expression can be used to match files by their full path. A string (or array of strings) can be used to match files by their file extension. Or a function can be used to perform more complex matching logic. See the custom parser docs for details. | ||
| */ | ||
| canParse?: boolean | RegExp | string | string[] | ((file: FileInfo) => boolean); | ||
| } | ||
| /** | ||
| * JSON Schema `$Ref` Parser supports plug-ins, such as resolvers and parsers. These plug-ins can have methods such as `canRead()`, `read()`, `canParse()`, and `parse()`. All of these methods accept the same object as their parameter: an object containing information about the file being read or parsed. | ||
| * | ||
| * The file info object currently only consists of a few properties, but it may grow in the future if plug-ins end up needing more information. | ||
| * | ||
| * See https://apitools.dev/json-schema-ref-parser/docs/plugins/file-info-object.html | ||
| */ | ||
| export interface FileInfo { | ||
| /** | ||
| * The full URL of the file. This could be any type of URL, including "http://", "https://", "file://", "ftp://", "mongodb://", or even a local filesystem path (when running in Node.js). | ||
| */ | ||
| url: string; | ||
| /** | ||
| * The lowercase file extension, such as ".json", ".yaml", ".txt", etc. | ||
| */ | ||
| extension: string; | ||
| /** | ||
| * The raw file contents, in whatever form they were returned by the resolver that read the file. | ||
| */ | ||
| data: string | Buffer; | ||
| } | ||
| /** | ||
| * When you call the resolve method, the value that gets passed to the callback function (or Promise) is a $Refs object. This same object is accessible via the parser.$refs property of OpenAPIParser objects. | ||
| * | ||
| * This object is a map of JSON References and their resolved values. It also has several convenient helper methods that make it easy for you to navigate and manipulate the JSON References. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/refs.html | ||
| */ | ||
| export class $Refs { | ||
| /** | ||
| * This property is true if the API definition contains any circular references. You may want to check this property before serializing the dereferenced API definition as JSON, since JSON.stringify() does not support circular references by default. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/refs.html#circular | ||
| */ | ||
| public circular: boolean; | ||
| /** | ||
| * Returns the paths/URLs of all the files in your API definition (including the main API definition file). | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/refs.html#pathstypes | ||
| * | ||
| * @param types (optional) Optionally only return certain types of paths ("file", "http", etc.) | ||
| */ | ||
| public paths(...types: string[]): string[]; | ||
| /** | ||
| * Returns a map of paths/URLs and their correspond values. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/refs.html#valuestypes | ||
| * | ||
| * @param types (optional) Optionally only return values from certain locations ("file", "http", etc.) | ||
| */ | ||
| public values(...types: string[]): { [url: string]: any }; | ||
| /** | ||
| * Returns `true` if the given path exists in the OpenAPI definition; otherwise, returns `false` | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/refs.html#existsref | ||
| * | ||
| * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash | ||
| */ | ||
| public exists($ref: string): boolean; | ||
| /** | ||
| * Gets the value at the given path in the OpenAPI definition. Throws an error if the path does not exist. | ||
| * | ||
| * See https://apitools.dev/swagger-parser/docs/refs.html#getref | ||
| * | ||
| * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash | ||
| */ | ||
| public get($ref: string): any; | ||
| /** | ||
| * Sets the value at the given path in the OpenAPI definition. If the property, or any of its parents, don't exist, they will be created. | ||
| * | ||
| * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash | ||
| * @param value The value to assign. Can be anything (object, string, number, etc.) | ||
| */ | ||
| public set($ref: string, value: any): void; | ||
| } | ||
| } |
-192
| /* eslint-disable no-unused-vars */ | ||
| const { ono } = require('@jsdevtools/ono'); | ||
| const $RefParser = require('@readme/json-schema-ref-parser'); | ||
| const dereference = require('@readme/json-schema-ref-parser/lib/dereference'); | ||
| const normalizeArgs = require('@readme/json-schema-ref-parser/lib/normalize-args'); | ||
| const maybe = require('call-me-maybe'); | ||
| const Options = require('./options'); | ||
| const util = require('./util'); | ||
| const validateSchema = require('./validators/schema'); | ||
| const validateSpec = require('./validators/spec'); | ||
| 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]; | ||
| module.exports = OpenAPIParser; | ||
| /** | ||
| * 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 | ||
| */ | ||
| function OpenAPIParser() { | ||
| $RefParser.apply(this, arguments); | ||
| } | ||
| util.inherits(OpenAPIParser, $RefParser); | ||
| OpenAPIParser.parse = $RefParser.parse; | ||
| OpenAPIParser.resolve = $RefParser.resolve; | ||
| OpenAPIParser.bundle = $RefParser.bundle; | ||
| OpenAPIParser.dereference = $RefParser.dereference; | ||
| /** | ||
| * Alias {@link $RefParser#schema} as {@link OpenAPIParser#api} | ||
| */ | ||
| Object.defineProperty(OpenAPIParser.prototype, 'api', { | ||
| configurable: true, | ||
| enumerable: true, | ||
| get() { | ||
| return this.schema; | ||
| }, | ||
| }); | ||
| /** | ||
| * 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. | ||
| */ | ||
| OpenAPIParser.prototype.parse = async function (path, api, options, callback) { | ||
| const args = normalizeArgs(arguments); | ||
| args.options = new Options(args.options); | ||
| try { | ||
| const schema = await $RefParser.prototype.parse.call(this, 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 || 'Supplied 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 an OpenAPI definition | ||
| if (schema.openapi === undefined || schema.info === undefined) { | ||
| throw ono.syntax(`${args.path || 'Supplied schema'} is not a valid OpenAPI definition.`); | ||
| } else if (schema.paths === undefined) { | ||
| if (supported31Versions.includes(schema.openapi)) { | ||
| if (schema.webhooks === undefined) { | ||
| throw ono.syntax(`${args.path || 'Supplied schema'} is not a valid OpenAPI definition.`); | ||
| } | ||
| } else { | ||
| throw ono.syntax(`${args.path || 'Supplied schema'} is not a valid OpenAPI 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. | ||
| */ | ||
| OpenAPIParser.validate = function (path, api, options, callback) { | ||
| const Class = this; | ||
| const instance = new Class(); | ||
| return instance.validate.apply(instance, arguments); | ||
| }; | ||
| /** | ||
| * 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. | ||
| */ | ||
| OpenAPIParser.prototype.validate = async function (path, api, options, callback) { | ||
| const me = this; | ||
| const 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) | ||
| const circular$RefOption = args.options.dereference.circular; | ||
| if (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, args.options); | ||
| 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)); | ||
| } | ||
| }; | ||
| /** | ||
| * The Swagger object | ||
| * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#swagger-object | ||
| * | ||
| * @typedef {{swagger: string, info: {}, paths: {}}} SwaggerObject | ||
| */ |
| const $RefParserOptions = require('@readme/json-schema-ref-parser/lib/options'); | ||
| const util = require('./util'); | ||
| const schemaValidator = require('./validators/schema'); | ||
| const specValidator = require('./validators/spec'); | ||
| module.exports = ParserOptions; | ||
| /** | ||
| * Options that determine how Swagger APIs are parsed, resolved, dereferenced, and validated. | ||
| * | ||
| * @param {object|ParserOptions} [_options] - Overridden options | ||
| * @class | ||
| * @augments $RefParserOptions | ||
| */ | ||
| // eslint-disable-next-line no-unused-vars | ||
| function ParserOptions(_options) { | ||
| $RefParserOptions.call(this, ParserOptions.defaults); | ||
| $RefParserOptions.apply(this, arguments); | ||
| } | ||
| ParserOptions.defaults = { | ||
| /** | ||
| * Determines how the API definition will be validated. | ||
| * | ||
| * You can add additional validators of your own, replace an existing one with | ||
| * your own implemenation, or disable any validator by setting it to false. | ||
| */ | ||
| validate: { | ||
| colorizeErrors: false, | ||
| schema: schemaValidator, | ||
| spec: specValidator, | ||
| }, | ||
| }; | ||
| util.inherits(ParserOptions, $RefParserOptions); |
-89
| // eslint-disable-next-line unicorn/import-style | ||
| const util = require('util'); | ||
| const url = require('@readme/json-schema-ref-parser/lib/util/url'); | ||
| exports.format = util.format; | ||
| exports.inherits = util.inherits; | ||
| /** | ||
| * Regular Expression that matches Swagger path params. | ||
| */ | ||
| exports.swaggerParamRegExp = /\{([^/}]+)}/g; | ||
| /** | ||
| * List of HTTP verbs used for OperationItem as per the Swagger specification | ||
| */ | ||
| const operationsList = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace']; | ||
| /** | ||
| * This function takes in a Server object, checks if it has relative path | ||
| * and then fixes it as per the path url | ||
| * | ||
| * @param {object} server - The server object to be fixed | ||
| * @param {string} path - The path (an http/https url) from where the file was downloaded | ||
| * @returns {object} - The fixed server object | ||
| */ | ||
| function fixServers(server, path) { | ||
| // Server url starting with "/" tells that it is not an http(s) url | ||
| if (server.url && server.url.startsWith('/')) { | ||
| const inUrl = url.parse(path); | ||
| const finalUrl = `${inUrl.protocol}//${inUrl.hostname}${server.url}`; | ||
| server.url = finalUrl; // eslint-disable-line no-param-reassign | ||
| } | ||
| return server; | ||
| } | ||
| /** | ||
| * This function helps fix the relative servers in the API definition file | ||
| * be at root, path or operation's level | ||
| */ | ||
| function fixOasRelativeServers(schema, filePath) { | ||
| if (schema.openapi && filePath && (filePath.startsWith('http:') || filePath.startsWith('https:'))) { | ||
| /** | ||
| * From OpenAPI v3 spec for Server object's url property: "REQUIRED. A URL to the target host. | ||
| * This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where | ||
| * the OpenAPI document is being served." | ||
| * Further, the spec says that "servers" property can show up at root level, in 'Path Item' object or in 'Operation' object. | ||
| * However, interpretation of the spec says that relative paths for servers should take into account the hostname that | ||
| * serves the OpenAPI file. | ||
| */ | ||
| if (schema.servers) { | ||
| schema.servers.map(server => fixServers(server, filePath)); // Root level servers array's fixup | ||
| } | ||
| // Path, Operation, or Webhook level servers array's fixup | ||
| ['paths', 'webhooks'].forEach(component => { | ||
| Object.keys(schema[component] || []).forEach(path => { | ||
| const pathItem = schema[component][path]; | ||
| Object.keys(pathItem).forEach(opItem => { | ||
| if (opItem === 'servers') { | ||
| // servers at pathitem level | ||
| pathItem[opItem].map(server => fixServers(server, filePath)); | ||
| } else if (operationsList.includes(opItem)) { | ||
| // servers at operation level | ||
| if (pathItem[opItem].servers) { | ||
| pathItem[opItem].servers.map(server => fixServers(server, filePath)); | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
| }); | ||
| } else { | ||
| // Do nothing and return | ||
| } | ||
| } | ||
| /** | ||
| * Determine the proper name for the API specification schema used by a given schema. | ||
| * | ||
| * @param {object} schema | ||
| * @returns {string} - The name of the specification that this schema utilizes. | ||
| */ | ||
| function getSpecificationName(schema) { | ||
| return schema.swagger ? 'Swagger' : 'OpenAPI'; | ||
| } | ||
| exports.fixOasRelativeServers = fixOasRelativeServers; | ||
| exports.getSpecificationName = getSpecificationName; |
| const { ono } = require('@jsdevtools/ono'); | ||
| const betterAjvErrors = require('@readme/better-ajv-errors'); | ||
| const { openapi } = require('@readme/openapi-schemas'); | ||
| const Ajv = require('ajv/dist/2020'); | ||
| const AjvDraft4 = require('ajv-draft-04'); | ||
| const { getSpecificationName } = require('../util'); | ||
| /** | ||
| * We've had issues with specs larger than 2MB+ with 1,000+ errors causing memory leaks so if we | ||
| * have a spec with more than `LARGE_SPEC_ERROR_CAP` errors and it's **stringified** length is | ||
| * larger than `LARGE_SPEC_LIMITS` then we will only return the first `LARGE_SPEC_ERROR_CAP` errors. | ||
| * | ||
| * Ideally we'd be looking at the byte size of the spec instead of looking at its stringified | ||
| * length value but the Blob API, which we'd use to get its size with `new Blob([str]).size;`, was | ||
| * only recently introduced in Node 15. | ||
| * | ||
| * w/r/t the 5,000,000 limit here: The spec we found causing these memory leaks had a size of | ||
| * 13,934,323 so 5mil seems like a decent cap to start with. | ||
| * | ||
| * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob} | ||
| */ | ||
| const LARGE_SPEC_ERROR_CAP = 20; | ||
| const LARGE_SPEC_SIZE_CAP = 5000000; | ||
| module.exports = validateSchema; | ||
| /** | ||
| * Validates the given Swagger API against the Swagger 2.0 or OpenAPI 3.0 and 3.1 schemas. | ||
| * | ||
| * @param {SwaggerObject} api | ||
| * @param {Object} options | ||
| */ | ||
| function validateSchema(api, options) { | ||
| let ajv; | ||
| // Choose the appropriate schema (Swagger or OpenAPI) | ||
| let schema; | ||
| if (api.swagger) { | ||
| schema = openapi.v2; | ||
| ajv = initializeAjv(); | ||
| } else if (api.openapi.startsWith('3.1')) { | ||
| schema = openapi.v31legacy; | ||
| // There's a bug with Ajv in how it handles `$dynamicRef` in the way that it's used within the 3.1 schema so we | ||
| // need to do some adhoc workarounds. | ||
| // https://github.com/OAI/OpenAPI-Specification/issues/2689 | ||
| // https://github.com/ajv-validator/ajv/issues/1573 | ||
| const schemaDynamicRef = schema.$defs.schema; | ||
| delete schemaDynamicRef.$dynamicAnchor; | ||
| schema.$defs.components.properties.schemas.additionalProperties = schemaDynamicRef; | ||
| schema.$defs.header.dependentSchemas.schema.properties.schema = schemaDynamicRef; | ||
| schema.$defs['media-type'].properties.schema = schemaDynamicRef; | ||
| schema.$defs.parameter.properties.schema = schemaDynamicRef; | ||
| ajv = initializeAjv(false); | ||
| } else { | ||
| schema = openapi.v3; | ||
| ajv = initializeAjv(); | ||
| } | ||
| // Validate against the schema | ||
| const isValid = ajv.validate(schema, api); | ||
| if (!isValid) { | ||
| const err = ajv.errors; | ||
| let additionalErrors = 0; | ||
| let reducedErrors = reduceAjvErrors(err); | ||
| const totalErrors = reducedErrors.length; | ||
| if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) { | ||
| try { | ||
| if (JSON.stringify(api).length >= LARGE_SPEC_SIZE_CAP) { | ||
| additionalErrors = reducedErrors.length - 20; | ||
| reducedErrors = reducedErrors.slice(0, 20); | ||
| } | ||
| } catch (error) { | ||
| // If we failed to stringify the API definition to look at its size then we should process | ||
| // all of its errors as-is. | ||
| } | ||
| } | ||
| let message = `${getSpecificationName(api)} schema validation failed.\n`; | ||
| message += '\n'; | ||
| message += betterAjvErrors(schema, api, reducedErrors, { | ||
| colorize: options.validate.colorizeErrors, | ||
| indent: 2, | ||
| }); | ||
| if (additionalErrors) { | ||
| message += '\n\n'; | ||
| message += `Plus an additional ${additionalErrors} errors. Please resolve the above and re-run validation to see more.`; | ||
| } | ||
| throw ono.syntax(err, { details: err, totalErrors }, message); | ||
| } | ||
| } | ||
| /** | ||
| * Determines which version of Ajv to load and prepares it for use. | ||
| * | ||
| * @param {bool} draft04 | ||
| * @returns {Ajv} | ||
| */ | ||
| function initializeAjv(draft04 = true) { | ||
| const opts = { | ||
| allErrors: true, | ||
| strict: false, | ||
| validateFormats: false, | ||
| }; | ||
| if (draft04) { | ||
| return new AjvDraft4(opts); | ||
| } | ||
| return new Ajv(opts); | ||
| } | ||
| /** | ||
| * Because of the way that Ajv works, if a validation error occurs deep within a schema there's a chance that errors | ||
| * will also be thrown for its immediate parents, leading to a case where we'll eventually show the error indecipherable | ||
| * errors like "$ref is missing here!" instance of what's _actually_ going on where they may have mistyped `enum` as | ||
| * `enumm`. | ||
| * | ||
| * To alleviate this confusing noise, we're compressing Ajv errors down to only surface the deepest point for each | ||
| * lineage, so that if a user typos `enum` as `enumm` we'll surface just that error for them (because really that's | ||
| * **the** error). | ||
| * | ||
| * @param {Array} errors | ||
| * @returns {Array} | ||
| */ | ||
| function reduceAjvErrors(errors) { | ||
| const flattened = new Map(); | ||
| errors.forEach(err => { | ||
| // These two errors appear when a child schema of them has a problem and instead of polluting the user with | ||
| // indecipherable noise we should instead relay the more specific error to them. If this is all that's present in | ||
| // the stack then as a safety net before we wrap up we'll just return the original `errors` stack. | ||
| if (["must have required property '$ref'", 'must match exactly one schema in oneOf'].includes(err.message)) { | ||
| return; | ||
| } | ||
| // If this is our first run through let's initialize our dataset and move along. | ||
| if (!flattened.size) { | ||
| flattened.set(err.instancePath, err); | ||
| return; | ||
| } else if (flattened.has(err.instancePath)) { | ||
| // If we already have an error recorded for this `instancePath` we can ignore it because we (likely) already have | ||
| // recorded the more specific error. | ||
| return; | ||
| } | ||
| // If this error hasn't already been recorded, maybe it's an error against the same `instancePath` stack, in which | ||
| // case we should ignore it because the more specific error has already been recorded. | ||
| let shouldRecordError = true; | ||
| flattened.forEach(flat => { | ||
| if (flat.instancePath.includes(err.instancePath)) { | ||
| shouldRecordError = false; | ||
| } | ||
| }); | ||
| if (shouldRecordError) { | ||
| flattened.set(err.instancePath, err); | ||
| } | ||
| }); | ||
| // If we weren't able to fold errors down for whatever reason just return the original stack. | ||
| if (!flattened.size) { | ||
| return errors; | ||
| } | ||
| return [...flattened.values()]; | ||
| } |
| const validateOpenAPI = require('./spec/openapi'); | ||
| const validateSwagger = require('./spec/swagger'); | ||
| /** | ||
| * Validates either a Swagger 2.0 or OpenAPI 3.x API definition against cases that aren't covered by their JSON Schema | ||
| * definitions. | ||
| * | ||
| * @param {SwaggerObject} api | ||
| */ | ||
| module.exports = function validateSpec(api) { | ||
| if (api.openapi) { | ||
| return validateOpenAPI(api); | ||
| } | ||
| return validateSwagger(api); | ||
| }; |
| const swaggerMethods = require('@apidevtools/swagger-methods'); | ||
| const { ono } = require('@jsdevtools/ono'); | ||
| const util = require('../../util'); | ||
| module.exports = validateSpec; | ||
| /** | ||
| * Validates parts of the OpenAPI 3.0 and 3.1 that aren't covered by their JSON Schema definitions. | ||
| * | ||
| * @todo This library currently does not validate required properties like the Swagger validator does due to some | ||
| * gnarly quirks with cases where a required property exists within a `oneOf` or `anyOf` (and within a child `allOf` | ||
| * of one of those). See https://api.apis.guru/v2/specs/twitter.com/labs/2.13/openapi.yaml for a good example. | ||
| * | ||
| * @param {SwaggerObject} api | ||
| * @link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md | ||
| * @link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md | ||
| */ | ||
| function validateSpec(api) { | ||
| const operationIds = []; | ||
| Object.keys(api.paths || {}).forEach(pathName => { | ||
| const path = api.paths[pathName]; | ||
| const pathId = `/paths${pathName}`; | ||
| if (path && pathName.indexOf('/') === 0) { | ||
| validatePath(api, path, pathId, operationIds); | ||
| } | ||
| }); | ||
| // There's a problem with how the 3.0 schema uses `patternProperties` for defining the format of scheme names that it | ||
| // ignores anything that doesn't match, so if you for example have a space in a schema name it'll be seen as valid | ||
| // when it should instead trigger a validation error. | ||
| // | ||
| // https://github.com/APIDevTools/swagger-parser/issues/184 | ||
| if (api.openapi.startsWith('3.0')) { | ||
| if (api.components) { | ||
| Object.keys(api.components).forEach(componentType => { | ||
| Object.keys(api.components[componentType]).forEach(componentName => { | ||
| const componentId = `/components/${componentType}/${componentName}`; | ||
| if (!/^[a-zA-Z0-9.\-_]+$/.test(componentName)) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${componentId} has an invalid name. Component names should match against: /^[a-zA-Z0-9.-_]+$/`, | ||
| ); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given path. | ||
| * | ||
| * @param {SwaggerObject} api - The entire OpenAPI API definition | ||
| * @param {object} path - A Path object, from the OpenAPI API definition | ||
| * @param {string} pathId - A value that uniquely identifies the path | ||
| * @param {string} operationIds - An array of collected operationIds found in other paths | ||
| */ | ||
| function validatePath(api, path, pathId, operationIds) { | ||
| // `@apidevtools/swagger-methods` doesn't ship a `trace` method so we need to improvise. | ||
| [...swaggerMethods, 'trace'].forEach(operationName => { | ||
| const operation = path[operationName]; | ||
| const operationId = `${pathId}/${operationName}`; | ||
| if (operation) { | ||
| const declaredOperationId = operation.operationId; | ||
| if (declaredOperationId) { | ||
| if (operationIds.indexOf(declaredOperationId) === -1) { | ||
| operationIds.push(declaredOperationId); | ||
| } else { | ||
| throw ono.syntax(`Validation failed. Duplicate operation id '${declaredOperationId}'`); | ||
| } | ||
| } | ||
| validateParameters(api, path, pathId, operation, operationId); | ||
| Object.keys(operation.responses || {}).forEach(responseCode => { | ||
| const response = operation.responses[responseCode]; | ||
| const responseId = `${operationId}/responses/${responseCode}`; | ||
| validateResponse(responseCode, response || {}, responseId); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the parameters for the given operation. | ||
| * | ||
| * @param {SwaggerObject} api - The entire Swagger API object | ||
| * @param {object} path - A Path object, from the Swagger API | ||
| * @param {string} pathId - A value that uniquely identifies the path | ||
| * @param {object} operation - An Operation object, from the Swagger API | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validateParameters(api, path, pathId, operation, operationId) { | ||
| const pathParams = path.parameters || []; | ||
| const operationParams = operation.parameters || []; | ||
| // Check for duplicate path parameters. | ||
| try { | ||
| checkForDuplicates(pathParams); | ||
| } catch (e) { | ||
| throw ono.syntax(e, `Validation failed. ${pathId} has duplicate parameters`); | ||
| } | ||
| // Check for duplicate operation parameters. | ||
| try { | ||
| checkForDuplicates(operationParams); | ||
| } catch (e) { | ||
| throw ono.syntax(e, `Validation failed. ${operationId} has duplicate parameters`); | ||
| } | ||
| // Combine the path and operation parameters, with the operation params taking precedence over the path params. | ||
| const params = pathParams.reduce((combinedParams, value) => { | ||
| const duplicate = combinedParams.some(param => { | ||
| return param.in === value.in && param.name === value.name; | ||
| }); | ||
| if (!duplicate) { | ||
| combinedParams.push(value); | ||
| } | ||
| return combinedParams; | ||
| }, operationParams.slice()); | ||
| validatePathParameters(params, pathId, operationId); | ||
| validateParameterTypes(params, api, operation, operationId); | ||
| } | ||
| /** | ||
| * Validates path parameters for the given path. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| * @param {string} pathId - A value that uniquely identifies the path | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validatePathParameters(params, pathId, operationId) { | ||
| // Find all `{placeholders}` in the path string. And because paths can have path parameters duped we need to convert | ||
| // this to a unique array so we can eliminate false positives of placeholders that might be duplicated. | ||
| const placeholders = [...new Set(pathId.match(util.swaggerParamRegExp) || [])]; | ||
| params | ||
| .filter(param => param.in === 'path') | ||
| .forEach(param => { | ||
| if (param.required !== true) { | ||
| throw ono.syntax( | ||
| 'Validation failed. Path parameters cannot be optional. ' + | ||
| `Set required=true for the "${param.name}" parameter at ${operationId}`, | ||
| ); | ||
| } | ||
| const match = placeholders.indexOf(`{${param.name}}`); | ||
| if (match === -1) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${operationId} has a path parameter named "${param.name}", ` + | ||
| `but there is no corresponding {${param.name}} in the path string`, | ||
| ); | ||
| } | ||
| placeholders.splice(match, 1); | ||
| }); | ||
| if (placeholders.length > 0) { | ||
| throw ono.syntax(`Validation failed. ${operationId} is missing path parameter(s) for ${placeholders}`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates data types of parameters for the given operation. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| * @param {object} api - The entire Swagger API object | ||
| * @param {object} operation - An Operation object, from the Swagger API | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validateParameterTypes(params, api, operation, operationId) { | ||
| params.forEach(param => { | ||
| /** | ||
| * @todo add better handling when `content` is present instead of `schema`. | ||
| * @link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-10 | ||
| * @link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-10 | ||
| */ | ||
| if (!param.schema && param.content) { | ||
| return; | ||
| } | ||
| const parameterId = `${operationId}/parameters/${param.name}`; | ||
| validateSchema(param.schema, parameterId); | ||
| }); | ||
| } | ||
| /** | ||
| * Checks the given parameter list for duplicates, and throws an error if found. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| */ | ||
| function checkForDuplicates(params) { | ||
| for (let i = 0; i < params.length - 1; i++) { | ||
| const outer = params[i]; | ||
| for (let j = i + 1; j < params.length; j++) { | ||
| const inner = params[j]; | ||
| if (outer.name === inner.name && outer.in === inner.in) { | ||
| throw ono.syntax(`Validation failed. Found multiple ${outer.in} parameters named "${outer.name}"`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given response object. | ||
| * | ||
| * @param {string} code - The HTTP response code (or "default") | ||
| * @param {object} response - A Response object, from the Swagger API | ||
| * @param {string} responseId - A value that uniquely identifies the response | ||
| */ | ||
| function validateResponse(code, response, responseId) { | ||
| Object.keys(response.headers || {}).forEach(headerName => { | ||
| const header = response.headers[headerName]; | ||
| const headerId = `${responseId}/headers/${headerName}`; | ||
| if (header.schema) { | ||
| validateSchema(header.schema, headerId); | ||
| } else if (header.content) { | ||
| Object.keys(header.content).forEach(mediaType => { | ||
| if (header.content[mediaType].schema) { | ||
| validateSchema(header.content[mediaType].schema || {}, `${headerId}/content/${mediaType}/schema`); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| if (response.content) { | ||
| Object.keys(response.content).forEach(mediaType => { | ||
| if (response.content[mediaType].schema) { | ||
| validateSchema(response.content[mediaType].schema || {}, `${responseId}/content/${mediaType}/schema`); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given Swagger schema object. | ||
| * | ||
| * @param {object} schema - A Schema object, from the Swagger API | ||
| * @param {string} schemaId - A value that uniquely identifies the schema object | ||
| */ | ||
| function validateSchema(schema, schemaId) { | ||
| if (schema.type === 'array' && !schema.items) { | ||
| throw ono.syntax(`Validation failed. ${schemaId} is an array, so it must include an "items" schema`); | ||
| } | ||
| } |
| const swaggerMethods = require('@apidevtools/swagger-methods'); | ||
| const { ono } = require('@jsdevtools/ono'); | ||
| const util = require('../../util'); | ||
| const primitiveTypes = ['array', 'boolean', 'integer', 'number', 'string']; | ||
| const schemaTypes = ['array', 'boolean', 'integer', 'number', 'string', 'object', 'null', undefined]; | ||
| module.exports = validateSpec; | ||
| /** | ||
| * Validates parts of the Swagger 2.0 spec that aren't covered by the Swagger 2.0 JSON Schema. | ||
| * | ||
| * @param {SwaggerObject} api | ||
| */ | ||
| function validateSpec(api) { | ||
| const operationIds = []; | ||
| Object.keys(api.paths || {}).forEach(pathName => { | ||
| const path = api.paths[pathName]; | ||
| const pathId = `/paths${pathName}`; | ||
| if (path && pathName.indexOf('/') === 0) { | ||
| validatePath(api, path, pathId, operationIds); | ||
| } | ||
| }); | ||
| Object.keys(api.definitions || {}).forEach(definitionName => { | ||
| const definition = api.definitions[definitionName]; | ||
| const definitionId = `/definitions/${definitionName}`; | ||
| if (!/^[a-zA-Z0-9.\-_]+$/.test(definitionName)) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${definitionId} has an invalid name. Definition names should match against: /^[a-zA-Z0-9.-_]+$/`, | ||
| ); | ||
| } | ||
| validateRequiredPropertiesExist(definition, definitionId); | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the given path. | ||
| * | ||
| * @param {SwaggerObject} api - The entire Swagger API object | ||
| * @param {object} path - A Path object, from the Swagger API | ||
| * @param {string} pathId - A value that uniquely identifies the path | ||
| * @param {string} operationIds - An array of collected operationIds found in other paths | ||
| */ | ||
| function validatePath(api, path, pathId, operationIds) { | ||
| swaggerMethods.forEach(operationName => { | ||
| const operation = path[operationName]; | ||
| const operationId = `${pathId}/${operationName}`; | ||
| if (operation) { | ||
| const declaredOperationId = operation.operationId; | ||
| if (declaredOperationId) { | ||
| if (operationIds.indexOf(declaredOperationId) === -1) { | ||
| operationIds.push(declaredOperationId); | ||
| } else { | ||
| throw ono.syntax(`Validation failed. Duplicate operation id '${declaredOperationId}'`); | ||
| } | ||
| } | ||
| validateParameters(api, path, pathId, operation, operationId); | ||
| Object.keys(operation.responses || {}).forEach(responseName => { | ||
| const response = operation.responses[responseName]; | ||
| const responseId = `${operationId}/responses/${responseName}`; | ||
| validateResponse(responseName, response || {}, responseId); | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Validates the parameters for the given operation. | ||
| * | ||
| * @param {SwaggerObject} api - The entire Swagger API object | ||
| * @param {object} path - A Path object, from the Swagger API | ||
| * @param {string} pathId - A value that uniquely identifies the path | ||
| * @param {object} operation - An Operation object, from the Swagger API | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validateParameters(api, path, pathId, operation, operationId) { | ||
| const pathParams = path.parameters || []; | ||
| const operationParams = operation.parameters || []; | ||
| // Check for duplicate path parameters | ||
| try { | ||
| checkForDuplicates(pathParams); | ||
| } catch (e) { | ||
| throw ono.syntax(e, `Validation failed. ${pathId} has duplicate parameters`); | ||
| } | ||
| // Check for duplicate operation parameters | ||
| try { | ||
| checkForDuplicates(operationParams); | ||
| } catch (e) { | ||
| throw ono.syntax(e, `Validation failed. ${operationId} has duplicate parameters`); | ||
| } | ||
| // Combine the path and operation parameters, | ||
| // with the operation params taking precedence over the path params | ||
| const params = pathParams.reduce((combinedParams, value) => { | ||
| const duplicate = combinedParams.some(param => { | ||
| return param.in === value.in && param.name === value.name; | ||
| }); | ||
| if (!duplicate) { | ||
| combinedParams.push(value); | ||
| } | ||
| return combinedParams; | ||
| }, operationParams.slice()); | ||
| validateBodyParameters(params, operationId); | ||
| validatePathParameters(params, pathId, operationId); | ||
| validateParameterTypes(params, api, operation, operationId); | ||
| } | ||
| /** | ||
| * Validates body and formData parameters for the given operation. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validateBodyParameters(params, operationId) { | ||
| const bodyParams = params.filter(param => { | ||
| return param.in === 'body'; | ||
| }); | ||
| const formParams = params.filter(param => { | ||
| return param.in === 'formData'; | ||
| }); | ||
| // There can only be one "body" parameter | ||
| if (bodyParams.length > 1) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${operationId} has ${bodyParams.length} body parameters. Only one is allowed.`, | ||
| ); | ||
| } else if (bodyParams.length > 0 && formParams.length > 0) { | ||
| // "body" params and "formData" params are mutually exclusive | ||
| throw ono.syntax( | ||
| `Validation failed. ${operationId} has body parameters and formData parameters. Only one or the other is allowed.`, | ||
| ); | ||
| } | ||
| } | ||
| /** | ||
| * Validates path parameters for the given path. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| * @param {string} pathId - A value that uniquely identifies the path | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validatePathParameters(params, pathId, operationId) { | ||
| // Find all {placeholders} in the path string | ||
| const placeholders = pathId.match(util.swaggerParamRegExp) || []; | ||
| // Check for duplicates | ||
| for (let i = 0; i < placeholders.length; i++) { | ||
| for (let j = i + 1; j < placeholders.length; j++) { | ||
| if (placeholders[i] === placeholders[j]) { | ||
| throw ono.syntax(`Validation failed. ${operationId} has multiple path placeholders named ${placeholders[i]}`); | ||
| } | ||
| } | ||
| } | ||
| params | ||
| .filter(param => param.in === 'path') | ||
| .forEach(param => { | ||
| if (param.required !== true) { | ||
| throw ono.syntax( | ||
| 'Validation failed. Path parameters cannot be optional. ' + | ||
| `Set required=true for the "${param.name}" parameter at ${operationId}`, | ||
| ); | ||
| } | ||
| const match = placeholders.indexOf(`{${param.name}}`); | ||
| if (match === -1) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${operationId} has a path parameter named "${param.name}", ` + | ||
| `but there is no corresponding {${param.name}} in the path string`, | ||
| ); | ||
| } | ||
| placeholders.splice(match, 1); | ||
| }); | ||
| if (placeholders.length > 0) { | ||
| throw ono.syntax(`Validation failed. ${operationId} is missing path parameter(s) for ${placeholders}`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates data types of parameters for the given operation. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| * @param {object} api - The entire Swagger API object | ||
| * @param {object} operation - An Operation object, from the Swagger API | ||
| * @param {string} operationId - A value that uniquely identifies the operation | ||
| */ | ||
| function validateParameterTypes(params, api, operation, operationId) { | ||
| params.forEach(param => { | ||
| const parameterId = `${operationId}/parameters/${param.name}`; | ||
| let schema; | ||
| let validTypes; | ||
| switch (param.in) { | ||
| case 'body': | ||
| schema = param.schema; | ||
| validTypes = schemaTypes; | ||
| break; | ||
| case 'formData': | ||
| schema = param; | ||
| validTypes = primitiveTypes.concat('file'); | ||
| break; | ||
| default: | ||
| schema = param; | ||
| validTypes = primitiveTypes; | ||
| } | ||
| validateSchema(schema, parameterId, validTypes); | ||
| validateRequiredPropertiesExist(schema, parameterId); | ||
| if (schema.type === 'file') { | ||
| // "file" params must consume at least one of these MIME types | ||
| const formData = /multipart\/(.*\+)?form-data/; | ||
| const urlEncoded = /application\/(.*\+)?x-www-form-urlencoded/; | ||
| const consumes = operation.consumes || api.consumes || []; | ||
| const hasValidMimeType = consumes.some(consume => { | ||
| return formData.test(consume) || urlEncoded.test(consume); | ||
| }); | ||
| if (!hasValidMimeType) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${operationId} has a file parameter, so it must consume multipart/form-data ` + | ||
| 'or application/x-www-form-urlencoded', | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Checks the given parameter list for duplicates, and throws an error if found. | ||
| * | ||
| * @param {object[]} params - An array of Parameter objects | ||
| */ | ||
| function checkForDuplicates(params) { | ||
| for (let i = 0; i < params.length - 1; i++) { | ||
| const outer = params[i]; | ||
| for (let j = i + 1; j < params.length; j++) { | ||
| const inner = params[j]; | ||
| if (outer.name === inner.name && outer.in === inner.in) { | ||
| throw ono.syntax(`Validation failed. Found multiple ${outer.in} parameters named "${outer.name}"`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given response object. | ||
| * | ||
| * @param {string} code - The HTTP response code (or "default") | ||
| * @param {object} response - A Response object, from the Swagger API | ||
| * @param {string} responseId - A value that uniquely identifies the response | ||
| */ | ||
| function validateResponse(code, response, responseId) { | ||
| if (code !== 'default' && (code < 100 || code > 599)) { | ||
| throw ono.syntax(`Validation failed. ${responseId} has an invalid response code (${code})`); | ||
| } | ||
| Object.keys(response.headers || {}).forEach(headerName => { | ||
| const header = response.headers[headerName]; | ||
| const headerId = `${responseId}/headers/${headerName}`; | ||
| validateSchema(header, headerId, primitiveTypes); | ||
| }); | ||
| if (response.schema) { | ||
| const validTypes = schemaTypes.concat('file'); | ||
| if (validTypes.indexOf(response.schema.type) === -1) { | ||
| throw ono.syntax( | ||
| `Validation failed. ${responseId} has an invalid response schema type (${response.schema.type})`, | ||
| ); | ||
| } else { | ||
| validateSchema(response.schema, `${responseId}/schema`, validTypes); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Validates the given Swagger schema object. | ||
| * | ||
| * @param {object} schema - A Schema object, from the Swagger API | ||
| * @param {string} schemaId - A value that uniquely identifies the schema object | ||
| * @param {string[]} validTypes - An array of the allowed schema types | ||
| */ | ||
| function validateSchema(schema, schemaId, validTypes) { | ||
| if (validTypes.indexOf(schema.type) === -1) { | ||
| throw ono.syntax(`Validation failed. ${schemaId} has an invalid type (${schema.type})`); | ||
| } | ||
| if (schema.type === 'array' && !schema.items) { | ||
| throw ono.syntax(`Validation failed. ${schemaId} is an array, so it must include an "items" schema`); | ||
| } | ||
| } | ||
| /** | ||
| * Validates that the declared properties of the given Swagger schema object actually exist. | ||
| * | ||
| * @param {object} schema - A Schema object, from the Swagger API | ||
| * @param {string} schemaId - A value that uniquely identifies the schema object | ||
| */ | ||
| function validateRequiredPropertiesExist(schema, schemaId) { | ||
| // Recursively collects all properties of the schema and its ancestors. They are added to the props object. | ||
| function collectProperties(schemaObj, props) { | ||
| if (schemaObj.properties) { | ||
| Object.keys(schemaObj.properties).forEach(property => { | ||
| // eslint-disable-next-line no-prototype-builtins | ||
| if (schemaObj.properties.hasOwnProperty(property)) { | ||
| // eslint-disable-next-line no-param-reassign | ||
| props[property] = schemaObj.properties[property]; | ||
| } | ||
| }); | ||
| } | ||
| if (schemaObj.allOf) { | ||
| schemaObj.allOf.forEach(parent => { | ||
| collectProperties(parent, props); | ||
| }); | ||
| } | ||
| } | ||
| if (schema.required && Array.isArray(schema.required)) { | ||
| const props = {}; | ||
| collectProperties(schema, props); | ||
| schema.required.forEach(requiredProperty => { | ||
| if (!props[requiredProperty]) { | ||
| throw ono.syntax( | ||
| `Validation failed. Property '${requiredProperty}' listed as required but does not exist in '${schemaId}'`, | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
217902
177.9%7
-22.22%6
-33.33%1815
20.2%0
-100%231
148.39%0
-100%Yes
NaN9
-18.18%1
Infinity%1
Infinity%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed