🚨 Active Supply Chain Attack:node-ipc Package Compromised.Learn More
Socket
Book a DemoSign in
Socket

@readme/openapi-parser

Package Overview
Dependencies
Maintainers
7
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@readme/openapi-parser - npm Package Compare versions

Comparing version
6.0.1
to
6.1.0
+266
-42
dist/index.cjs

@@ -110,4 +110,4 @@ "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;

var _openapischemas = require('@readme/openapi-schemas');
var _ajvdraft04 = require('ajv-draft-04'); var _ajvdraft042 = _interopRequireDefault(_ajvdraft04);
var _2020js = require('ajv/dist/2020.js'); var _2020js2 = _interopRequireDefault(_2020js);
var _ajvdraft04 = require('ajv-draft-04'); var _ajvdraft042 = _interopRequireDefault(_ajvdraft04);

@@ -165,3 +165,3 @@ // src/lib/hasInvalidPaths.ts

}
function validateSchema(api, options = {}) {
function validateSchema(api, options = {}, suppressedInstancePaths = []) {
if (hasInvalidPaths(api)) {

@@ -208,3 +208,8 @@ return {

let additionalErrors = 0;
let reducedErrors = reduceAjvErrors(ajv.errors);
let reducedErrors = reduceAjvErrors(ajv.errors).filter((err) => {
return !suppressedInstancePaths.some((path) => err.instancePath === path || err.instancePath.startsWith(`${path}/`));
});
if (!reducedErrors.length) {
return { valid: true, warnings: [], specification: specificationName };
}
if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) {

@@ -244,5 +249,10 @@ try {

// src/validators/spec/index.ts
var SpecificationValidator = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this); }
var SpecificationValidator = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this); }
__init() {this.errors = []}
__init2() {this.warnings = []}
/**
* Instance paths flagged by `runPreSchemaChecks()`. Used to suppress AJV `oneOf` noise that
* the pre-schema validator has already produced a clearer error or warning for.
*/
__init3() {this.flaggedInstancePaths = []}
reportError(message) {

@@ -254,2 +264,7 @@ this.errors.push({ message });

}
flagInstancePath(path) {
if (!this.flaggedInstancePaths.includes(path)) {
this.flaggedInstancePaths.push(path);
}
}
}, _class);

@@ -266,2 +281,5 @@

}
runPreSchemaChecks() {
this.checkSecuritySchemes();
}
run() {

@@ -482,2 +500,115 @@ const operationIds = [];

/**
* Validates security schemes in `components.securitySchemes` against their declared `type`.
*
* AJV uses a `oneOf` schema to validate security schemes, so when a scheme is malformed AJV
* fails every branch and produces overwhelming, unhelpful errors. This pre-AJV pass surfaces
* a single targeted error per problem.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object}
*/
checkSecuritySchemes() {
const securitySchemes = _optionalChain([this, 'access', _14 => _14.api, 'access', _15 => _15.components, 'optionalAccess', _16 => _16.securitySchemes]);
if (!securitySchemes) {
return;
}
const schemeTypeProps = {
apiKey: {
required: ["name", "in"],
foreign: { scheme: "http", bearerFormat: "http", flows: "oauth2", openIdConnectUrl: "openIdConnect" }
},
http: {
required: ["scheme"],
foreign: { name: "apiKey", in: "apiKey", flows: "oauth2", openIdConnectUrl: "openIdConnect" }
},
oauth2: {
required: ["flows"],
foreign: {
name: "apiKey",
in: "apiKey",
scheme: "http",
bearerFormat: "http",
openIdConnectUrl: "openIdConnect"
}
},
openIdConnect: {
required: ["openIdConnectUrl"],
foreign: { name: "apiKey", in: "apiKey", scheme: "http", bearerFormat: "http", flows: "oauth2" }
},
mutualTLS: {
required: [],
foreign: {
name: "apiKey",
in: "apiKey",
scheme: "http",
bearerFormat: "http",
flows: "oauth2",
openIdConnectUrl: "openIdConnect"
}
}
};
Object.keys(securitySchemes).forEach((name) => {
const scheme = securitySchemes[name];
if ("$ref" in scheme) {
return;
}
const schemeId = `/components/securitySchemes/${name}`;
const reportIssue = (message) => this.reportSecuritySchemeIssue(message, schemeId);
if (!("type" in scheme) || !scheme.type) {
reportIssue(
`\`${schemeId}\` is missing required property \`type\`. Must be one of: \`apiKey\`, \`http\`, \`oauth2\`, \`openIdConnect\`, \`mutualTLS\`.`
);
return;
}
const type = scheme.type;
if (type === "basic") {
reportIssue(
`\`${schemeId}\` uses \`type: basic\`, which is a Swagger 2.0 value. In OpenAPI 3.x use \`type: http\` with \`scheme: basic\` instead.`
);
return;
}
if (!(type in schemeTypeProps)) {
reportIssue(
`\`${schemeId}\` has an invalid \`type\`: \`${type}\`. Must be one of: \`apiKey\`, \`http\`, \`oauth2\`, \`openIdConnect\`, \`mutualTLS\`.`
);
return;
}
const config = schemeTypeProps[type];
config.required.forEach((prop) => {
if (!(prop in scheme)) {
reportIssue(`\`${schemeId}\` (\`type: ${type}\`) is missing required property \`${prop}\`.`);
}
});
Object.entries(config.foreign).forEach(([prop, ownerType]) => {
if (prop in scheme) {
reportIssue(
`\`${schemeId}\` (\`type: ${type}\`) includes \`${prop}\`, which is only valid for \`type: ${ownerType}\` schemes.`
);
}
});
if (type === "apiKey" && typeof scheme.in === "string") {
const validIn = ["query", "header", "cookie"];
if (!validIn.includes(scheme.in)) {
reportIssue(
`\`${schemeId}\` has an invalid \`in\` value: \`${scheme.in}\`. Must be one of: \`query\`, \`header\`, \`cookie\`.`
);
}
}
if (type === "oauth2" && scheme.flows && typeof scheme.flows === "object") {
if (Object.keys(scheme.flows).length === 0) {
reportIssue(
`\`${schemeId}\` has empty \`flows\`. At least one grant type is required: \`implicit\`, \`password\`, \`clientCredentials\`, or \`authorizationCode\`.`
);
}
}
});
}
reportSecuritySchemeIssue(message, schemeId) {
this.flagInstancePath(schemeId);
if (this.rules["invalid-security-scheme-properties"] === "warning") {
this.reportWarning(message);
} else {
this.reportError(message);
}
}
/**
* Checks the given parameter list for duplicates.

@@ -516,2 +647,5 @@ *

}
runPreSchemaChecks() {
this.checkSecurityDefinitions();
}
run() {

@@ -733,19 +867,5 @@ const operationIds = [];

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);
this.collectProperties(schema, props);
schema.required.forEach((requiredProperty) => {

@@ -764,2 +884,62 @@ if (!props[requiredProperty]) {

/**
* Recursively collects all properties of a schema and its ancestors. They are added to the
* supplied `props` object.
*
*/
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) => {
this.collectProperties(parent, props);
});
}
}
/**
* Validates security definitions against their declared `type`.
*
* AJV uses `oneOf` to validate `securityDefinitions`, so when a definition is malformed AJV
* fails every branch and produces overwhelming, unhelpful errors. This pre-AJV pass surfaces
* a single targeted error per problem.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-scheme-object}
*/
checkSecurityDefinitions() {
const securityDefinitions = this.api.securityDefinitions;
if (!securityDefinitions) {
return;
}
const validApiKeyIn = ["query", "header"];
Object.keys(securityDefinitions).forEach((name) => {
const definition = securityDefinitions[name];
const definitionId = `/securityDefinitions/${name}`;
const reportIssue = (message) => this.reportSecuritySchemeIssue(message, definitionId);
const type = definition.type;
if (type === "http") {
reportIssue(
`\`${definitionId}\` uses \`type: http\`, which is an OpenAPI 3.x value. In Swagger 2.0 use \`type: basic\` for HTTP Basic auth.`
);
return;
}
if (type === "apiKey" && typeof definition.in === "string" && !validApiKeyIn.includes(definition.in)) {
reportIssue(
`\`${definitionId}\` has an invalid \`in\` value: \`${definition.in}\`. Swagger 2.0 only supports \`query\` or \`header\`.`
);
}
});
}
reportSecuritySchemeIssue(message, definitionId) {
this.flagInstancePath(definitionId);
if (this.rules["invalid-security-scheme-properties"] === "warning") {
this.reportWarning(message);
} else {
this.reportError(message);
}
}
/**
* Checks the given parameter list for duplicates.

@@ -811,2 +991,33 @@ *

}
function validateSpecPreSchema(api, rules) {
let validator;
const specificationName = getSpecificationName(api);
if (_chunkLU5KN3DHcjs.isOpenAPI.call(void 0, api)) {
validator = new OpenAPISpecificationValidator(api, rules.openapi);
} else {
validator = new SwaggerSpecificationValidator(api, rules.swagger);
}
validator.runPreSchemaChecks();
const flaggedInstancePaths = validator.flaggedInstancePaths;
if (!validator.errors.length) {
return {
flaggedInstancePaths,
result: {
valid: true,
warnings: validator.warnings,
specification: specificationName
}
};
}
return {
flaggedInstancePaths,
result: {
valid: false,
errors: validator.errors,
warnings: validator.warnings,
additionalErrors: 0,
specification: specificationName
}
};
}

@@ -869,7 +1080,37 @@ // src/index.ts

parserOptions.dereference.circular = circular$RefOption;
result = validateSchema(parser.schema, options);
const openapiRules = _optionalChain([options, 'optionalAccess', _17 => _17.validate, 'optionalAccess', _18 => _18.rules, 'optionalAccess', _19 => _19.openapi]);
const swaggerRules = _optionalChain([options, 'optionalAccess', _20 => _20.validate, 'optionalAccess', _21 => _21.rules, 'optionalAccess', _22 => _22.swagger]);
const rules = {
openapi: {
"array-without-items": _optionalChain([openapiRules, 'optionalAccess', _23 => _23["array-without-items"]]) || "error",
"duplicate-non-request-body-parameters": _optionalChain([openapiRules, 'optionalAccess', _24 => _24["duplicate-non-request-body-parameters"]]) || "error",
"duplicate-operation-id": _optionalChain([openapiRules, 'optionalAccess', _25 => _25["duplicate-operation-id"]]) || "error",
"invalid-security-scheme-properties": _optionalChain([openapiRules, 'optionalAccess', _26 => _26["invalid-security-scheme-properties"]]) || "error",
"non-optional-path-parameters": _optionalChain([openapiRules, 'optionalAccess', _27 => _27["non-optional-path-parameters"]]) || "error",
"path-parameters-not-in-parameters": _optionalChain([openapiRules, 'optionalAccess', _28 => _28["path-parameters-not-in-parameters"]]) || "error",
"path-parameters-not-in-path": _optionalChain([openapiRules, 'optionalAccess', _29 => _29["path-parameters-not-in-path"]]) || "error"
},
swagger: {
"array-without-items": _optionalChain([swaggerRules, 'optionalAccess', _30 => _30["array-without-items"]]) || "error",
"duplicate-non-request-body-parameters": _optionalChain([swaggerRules, 'optionalAccess', _31 => _31["duplicate-non-request-body-parameters"]]) || "error",
"duplicate-operation-id": _optionalChain([swaggerRules, 'optionalAccess', _32 => _32["duplicate-operation-id"]]) || "error",
"invalid-security-scheme-properties": _optionalChain([swaggerRules, 'optionalAccess', _33 => _33["invalid-security-scheme-properties"]]) || "error",
"non-optional-path-parameters": _optionalChain([swaggerRules, 'optionalAccess', _34 => _34["non-optional-path-parameters"]]) || "error",
"path-parameters-not-in-parameters": _optionalChain([swaggerRules, 'optionalAccess', _35 => _35["path-parameters-not-in-parameters"]]) || "error",
"path-parameters-not-in-path": _optionalChain([swaggerRules, 'optionalAccess', _36 => _36["path-parameters-not-in-path"]]) || "error",
"unknown-required-schema-property": _optionalChain([swaggerRules, 'optionalAccess', _37 => _37["unknown-required-schema-property"]]) || "error"
}
};
const { result: preSchemaResult, flaggedInstancePaths } = validateSpecPreSchema(parser.schema, rules);
if (!preSchemaResult.valid) {
return preSchemaResult;
}
result = validateSchema(parser.schema, options, flaggedInstancePaths);
if (!result.valid) {
if (preSchemaResult.warnings.length) {
result.warnings = [...preSchemaResult.warnings, ...result.warnings];
}
return result;
}
if (_optionalChain([parser, 'access', _14 => _14.$refs, 'optionalAccess', _15 => _15.circular])) {
if (_optionalChain([parser, 'access', _38 => _38.$refs, 'optionalAccess', _39 => _39.circular])) {
if (circular$RefOption === true) {

@@ -883,23 +1124,6 @@ _jsonschemarefparser.dereferenceInternal.call(void 0, parser, parserOptions);

}
const openapiRules = _optionalChain([options, 'optionalAccess', _16 => _16.validate, 'optionalAccess', _17 => _17.rules, 'optionalAccess', _18 => _18.openapi]);
const swaggerRules = _optionalChain([options, 'optionalAccess', _19 => _19.validate, 'optionalAccess', _20 => _20.rules, 'optionalAccess', _21 => _21.swagger]);
result = validateSpec(parser.schema, {
openapi: {
"array-without-items": _optionalChain([openapiRules, 'optionalAccess', _22 => _22["array-without-items"]]) || "error",
"duplicate-non-request-body-parameters": _optionalChain([openapiRules, 'optionalAccess', _23 => _23["duplicate-non-request-body-parameters"]]) || "error",
"duplicate-operation-id": _optionalChain([openapiRules, 'optionalAccess', _24 => _24["duplicate-operation-id"]]) || "error",
"non-optional-path-parameters": _optionalChain([openapiRules, 'optionalAccess', _25 => _25["non-optional-path-parameters"]]) || "error",
"path-parameters-not-in-parameters": _optionalChain([openapiRules, 'optionalAccess', _26 => _26["path-parameters-not-in-parameters"]]) || "error",
"path-parameters-not-in-path": _optionalChain([openapiRules, 'optionalAccess', _27 => _27["path-parameters-not-in-path"]]) || "error"
},
swagger: {
"array-without-items": _optionalChain([swaggerRules, 'optionalAccess', _28 => _28["array-without-items"]]) || "error",
"duplicate-non-request-body-parameters": _optionalChain([swaggerRules, 'optionalAccess', _29 => _29["duplicate-non-request-body-parameters"]]) || "error",
"duplicate-operation-id": _optionalChain([swaggerRules, 'optionalAccess', _30 => _30["duplicate-operation-id"]]) || "error",
"non-optional-path-parameters": _optionalChain([swaggerRules, 'optionalAccess', _31 => _31["non-optional-path-parameters"]]) || "error",
"path-parameters-not-in-parameters": _optionalChain([swaggerRules, 'optionalAccess', _32 => _32["path-parameters-not-in-parameters"]]) || "error",
"path-parameters-not-in-path": _optionalChain([swaggerRules, 'optionalAccess', _33 => _33["path-parameters-not-in-path"]]) || "error",
"unknown-required-schema-property": _optionalChain([swaggerRules, 'optionalAccess', _34 => _34["unknown-required-schema-property"]]) || "error"
}
});
result = validateSpec(parser.schema, rules);
if (preSchemaResult.warnings.length) {
result.warnings = [...preSchemaResult.warnings, ...result.warnings];
}
return result;

@@ -906,0 +1130,0 @@ }

@@ -44,2 +44,10 @@ import { JSONSchema4Object, JSONSchema6Object, JSONSchema7Object } from 'json-schema';

/**
* Security schemes must only contain properties valid for their declared `type`. Catches
* common mistakes like an `http` scheme having a `name` (which belongs to `apiKey`) before
* AJV has a chance to surface confusing `oneOf` noise. The default is `error`.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object}
*/
'invalid-security-scheme-properties': 'error' | 'warning';
/**
* Parameters that are defined within the path URI must be specified as being `required`. The

@@ -87,2 +95,10 @@ * default is `error`.

/**
* Security definitions must only contain properties valid for their declared `type`. Catches
* common mistakes like an `apiKey` definition with `in: cookie` (which is OAS 3.0+ only)
* before AJV has a chance to surface confusing `oneOf` noise. The default is `error`.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-scheme-object}
*/
'invalid-security-scheme-properties': 'error' | 'warning';
/**
* Parameters that are defined within the path URI must be specified as being `required`. The

@@ -89,0 +105,0 @@ * default is `error`.

@@ -44,2 +44,10 @@ import { JSONSchema4Object, JSONSchema6Object, JSONSchema7Object } from 'json-schema';

/**
* Security schemes must only contain properties valid for their declared `type`. Catches
* common mistakes like an `http` scheme having a `name` (which belongs to `apiKey`) before
* AJV has a chance to surface confusing `oneOf` noise. The default is `error`.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object}
*/
'invalid-security-scheme-properties': 'error' | 'warning';
/**
* Parameters that are defined within the path URI must be specified as being `required`. The

@@ -87,2 +95,10 @@ * default is `error`.

/**
* Security definitions must only contain properties valid for their declared `type`. Catches
* common mistakes like an `apiKey` definition with `in: cookie` (which is OAS 3.0+ only)
* before AJV has a chance to surface confusing `oneOf` noise. The default is `error`.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-scheme-object}
*/
'invalid-security-scheme-properties': 'error' | 'warning';
/**
* Parameters that are defined within the path URI must be specified as being `required`. The

@@ -89,0 +105,0 @@ * default is `error`.

@@ -110,4 +110,4 @@ import {

import { openapi } from "@readme/openapi-schemas";
import AjvDraft4 from "ajv-draft-04";
import Ajv from "ajv/dist/2020.js";
import AjvDraft4 from "ajv-draft-04";

@@ -165,3 +165,3 @@ // src/lib/hasInvalidPaths.ts

}
function validateSchema(api, options = {}) {
function validateSchema(api, options = {}, suppressedInstancePaths = []) {
if (hasInvalidPaths(api)) {

@@ -208,3 +208,8 @@ return {

let additionalErrors = 0;
let reducedErrors = reduceAjvErrors(ajv.errors);
let reducedErrors = reduceAjvErrors(ajv.errors).filter((err) => {
return !suppressedInstancePaths.some((path) => err.instancePath === path || err.instancePath.startsWith(`${path}/`));
});
if (!reducedErrors.length) {
return { valid: true, warnings: [], specification: specificationName };
}
if (reducedErrors.length >= LARGE_SPEC_ERROR_CAP) {

@@ -247,2 +252,7 @@ try {

warnings = [];
/**
* Instance paths flagged by `runPreSchemaChecks()`. Used to suppress AJV `oneOf` noise that
* the pre-schema validator has already produced a clearer error or warning for.
*/
flaggedInstancePaths = [];
reportError(message) {

@@ -254,2 +264,7 @@ this.errors.push({ message });

}
flagInstancePath(path) {
if (!this.flaggedInstancePaths.includes(path)) {
this.flaggedInstancePaths.push(path);
}
}
};

@@ -266,2 +281,5 @@

}
runPreSchemaChecks() {
this.checkSecuritySchemes();
}
run() {

@@ -482,2 +500,115 @@ const operationIds = [];

/**
* Validates security schemes in `components.securitySchemes` against their declared `type`.
*
* AJV uses a `oneOf` schema to validate security schemes, so when a scheme is malformed AJV
* fails every branch and produces overwhelming, unhelpful errors. This pre-AJV pass surfaces
* a single targeted error per problem.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object}
*/
checkSecuritySchemes() {
const securitySchemes = this.api.components?.securitySchemes;
if (!securitySchemes) {
return;
}
const schemeTypeProps = {
apiKey: {
required: ["name", "in"],
foreign: { scheme: "http", bearerFormat: "http", flows: "oauth2", openIdConnectUrl: "openIdConnect" }
},
http: {
required: ["scheme"],
foreign: { name: "apiKey", in: "apiKey", flows: "oauth2", openIdConnectUrl: "openIdConnect" }
},
oauth2: {
required: ["flows"],
foreign: {
name: "apiKey",
in: "apiKey",
scheme: "http",
bearerFormat: "http",
openIdConnectUrl: "openIdConnect"
}
},
openIdConnect: {
required: ["openIdConnectUrl"],
foreign: { name: "apiKey", in: "apiKey", scheme: "http", bearerFormat: "http", flows: "oauth2" }
},
mutualTLS: {
required: [],
foreign: {
name: "apiKey",
in: "apiKey",
scheme: "http",
bearerFormat: "http",
flows: "oauth2",
openIdConnectUrl: "openIdConnect"
}
}
};
Object.keys(securitySchemes).forEach((name) => {
const scheme = securitySchemes[name];
if ("$ref" in scheme) {
return;
}
const schemeId = `/components/securitySchemes/${name}`;
const reportIssue = (message) => this.reportSecuritySchemeIssue(message, schemeId);
if (!("type" in scheme) || !scheme.type) {
reportIssue(
`\`${schemeId}\` is missing required property \`type\`. Must be one of: \`apiKey\`, \`http\`, \`oauth2\`, \`openIdConnect\`, \`mutualTLS\`.`
);
return;
}
const type = scheme.type;
if (type === "basic") {
reportIssue(
`\`${schemeId}\` uses \`type: basic\`, which is a Swagger 2.0 value. In OpenAPI 3.x use \`type: http\` with \`scheme: basic\` instead.`
);
return;
}
if (!(type in schemeTypeProps)) {
reportIssue(
`\`${schemeId}\` has an invalid \`type\`: \`${type}\`. Must be one of: \`apiKey\`, \`http\`, \`oauth2\`, \`openIdConnect\`, \`mutualTLS\`.`
);
return;
}
const config = schemeTypeProps[type];
config.required.forEach((prop) => {
if (!(prop in scheme)) {
reportIssue(`\`${schemeId}\` (\`type: ${type}\`) is missing required property \`${prop}\`.`);
}
});
Object.entries(config.foreign).forEach(([prop, ownerType]) => {
if (prop in scheme) {
reportIssue(
`\`${schemeId}\` (\`type: ${type}\`) includes \`${prop}\`, which is only valid for \`type: ${ownerType}\` schemes.`
);
}
});
if (type === "apiKey" && typeof scheme.in === "string") {
const validIn = ["query", "header", "cookie"];
if (!validIn.includes(scheme.in)) {
reportIssue(
`\`${schemeId}\` has an invalid \`in\` value: \`${scheme.in}\`. Must be one of: \`query\`, \`header\`, \`cookie\`.`
);
}
}
if (type === "oauth2" && scheme.flows && typeof scheme.flows === "object") {
if (Object.keys(scheme.flows).length === 0) {
reportIssue(
`\`${schemeId}\` has empty \`flows\`. At least one grant type is required: \`implicit\`, \`password\`, \`clientCredentials\`, or \`authorizationCode\`.`
);
}
}
});
}
reportSecuritySchemeIssue(message, schemeId) {
this.flagInstancePath(schemeId);
if (this.rules["invalid-security-scheme-properties"] === "warning") {
this.reportWarning(message);
} else {
this.reportError(message);
}
}
/**
* Checks the given parameter list for duplicates.

@@ -516,2 +647,5 @@ *

}
runPreSchemaChecks() {
this.checkSecurityDefinitions();
}
run() {

@@ -733,19 +867,5 @@ const operationIds = [];

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);
this.collectProperties(schema, props);
schema.required.forEach((requiredProperty) => {

@@ -764,2 +884,62 @@ if (!props[requiredProperty]) {

/**
* Recursively collects all properties of a schema and its ancestors. They are added to the
* supplied `props` object.
*
*/
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) => {
this.collectProperties(parent, props);
});
}
}
/**
* Validates security definitions against their declared `type`.
*
* AJV uses `oneOf` to validate `securityDefinitions`, so when a definition is malformed AJV
* fails every branch and produces overwhelming, unhelpful errors. This pre-AJV pass surfaces
* a single targeted error per problem.
*
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#security-scheme-object}
*/
checkSecurityDefinitions() {
const securityDefinitions = this.api.securityDefinitions;
if (!securityDefinitions) {
return;
}
const validApiKeyIn = ["query", "header"];
Object.keys(securityDefinitions).forEach((name) => {
const definition = securityDefinitions[name];
const definitionId = `/securityDefinitions/${name}`;
const reportIssue = (message) => this.reportSecuritySchemeIssue(message, definitionId);
const type = definition.type;
if (type === "http") {
reportIssue(
`\`${definitionId}\` uses \`type: http\`, which is an OpenAPI 3.x value. In Swagger 2.0 use \`type: basic\` for HTTP Basic auth.`
);
return;
}
if (type === "apiKey" && typeof definition.in === "string" && !validApiKeyIn.includes(definition.in)) {
reportIssue(
`\`${definitionId}\` has an invalid \`in\` value: \`${definition.in}\`. Swagger 2.0 only supports \`query\` or \`header\`.`
);
}
});
}
reportSecuritySchemeIssue(message, definitionId) {
this.flagInstancePath(definitionId);
if (this.rules["invalid-security-scheme-properties"] === "warning") {
this.reportWarning(message);
} else {
this.reportError(message);
}
}
/**
* Checks the given parameter list for duplicates.

@@ -811,2 +991,33 @@ *

}
function validateSpecPreSchema(api, rules) {
let validator;
const specificationName = getSpecificationName(api);
if (isOpenAPI(api)) {
validator = new OpenAPISpecificationValidator(api, rules.openapi);
} else {
validator = new SwaggerSpecificationValidator(api, rules.swagger);
}
validator.runPreSchemaChecks();
const flaggedInstancePaths = validator.flaggedInstancePaths;
if (!validator.errors.length) {
return {
flaggedInstancePaths,
result: {
valid: true,
warnings: validator.warnings,
specification: specificationName
}
};
}
return {
flaggedInstancePaths,
result: {
valid: false,
errors: validator.errors,
warnings: validator.warnings,
additionalErrors: 0,
specification: specificationName
}
};
}

@@ -869,18 +1080,5 @@ // src/index.ts

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 openapiRules = options?.validate?.rules?.openapi;
const swaggerRules = options?.validate?.rules?.swagger;
result = validateSpec(parser.schema, {
const rules = {
openapi: {

@@ -890,2 +1088,3 @@ "array-without-items": openapiRules?.["array-without-items"] || "error",

"duplicate-operation-id": openapiRules?.["duplicate-operation-id"] || "error",
"invalid-security-scheme-properties": openapiRules?.["invalid-security-scheme-properties"] || "error",
"non-optional-path-parameters": openapiRules?.["non-optional-path-parameters"] || "error",

@@ -899,2 +1098,3 @@ "path-parameters-not-in-parameters": openapiRules?.["path-parameters-not-in-parameters"] || "error",

"duplicate-operation-id": swaggerRules?.["duplicate-operation-id"] || "error",
"invalid-security-scheme-properties": swaggerRules?.["invalid-security-scheme-properties"] || "error",
"non-optional-path-parameters": swaggerRules?.["non-optional-path-parameters"] || "error",

@@ -905,3 +1105,27 @@ "path-parameters-not-in-parameters": swaggerRules?.["path-parameters-not-in-parameters"] || "error",

}
});
};
const { result: preSchemaResult, flaggedInstancePaths } = validateSpecPreSchema(parser.schema, rules);
if (!preSchemaResult.valid) {
return preSchemaResult;
}
result = validateSchema(parser.schema, options, flaggedInstancePaths);
if (!result.valid) {
if (preSchemaResult.warnings.length) {
result.warnings = [...preSchemaResult.warnings, ...result.warnings];
}
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."
);
}
}
result = validateSpec(parser.schema, rules);
if (preSchemaResult.warnings.length) {
result.warnings = [...preSchemaResult.warnings, ...result.warnings];
}
return result;

@@ -908,0 +1132,0 @@ }

+2
-4
{
"name": "@readme/openapi-parser",
"version": "6.0.1",
"version": "6.1.0",
"description": "Swagger 2.0 and OpenAPI 3.x parser and validator for Node and browsers",

@@ -81,5 +81,3 @@ "license": "MIT",

"vitest": "^4.0.8"
},
"prettier": "@readme/standards/prettier",
"gitHead": "766e5550ef968794a0d275bda5c47631a927da04"
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display