Files
seasonedShows/.stoplight/custom-functions/oasOpSecurityDefined.js
2022-08-18 20:08:26 +02:00

142 lines
3.1 KiB
JavaScript

import { isPlainObject } from '@stoplight/json';
import { createRulesetFunction } from '@stoplight/spectral-core';
function isObject(value) {
return value !== null && typeof value === 'object';
}
const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];
function* getAllOperations(paths) {
if (!isPlainObject(paths)) {
return;
}
const item = {
path: '',
operation: '',
value: null,
};
for (const path of Object.keys(paths)) {
const operations = paths[path];
if (!isPlainObject(operations)) {
continue;
}
item.path = path;
for (const operation of Object.keys(operations)) {
if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {
continue;
}
item.operation = operation;
item.value = operations[operation];
yield item;
}
}
}
function _get(value, path) {
for (const segment of path) {
if (!isObject(value)) {
break;
}
value = value[segment];
}
return value;
}
export default createRulesetFunction(
{
input: {
type: 'object',
properties: {
paths: {
type: 'object',
},
security: {
type: 'array',
},
},
},
options: {
type: 'object',
properties: {
schemesPath: {
type: 'array',
items: {
type: ['string', 'number'],
},
},
},
},
},
function oasOpSecurityDefined(targetVal, { schemesPath }) {
const { paths } = targetVal;
const results = [];
const schemes = _get(targetVal, schemesPath);
const allDefs = isObject(schemes) ? Object.keys(schemes) : [];
// Check global security requirements
const { security } = targetVal;
if (Array.isArray(security)) {
for (const [index, value] of security.entries()) {
if (!isObject(value)) {
continue;
}
const securityKeys = Object.keys(value);
for (const securityKey of securityKeys) {
if (!allDefs.includes(securityKey)) {
results.push({
message: `API "security" values must match a scheme defined in the "${schemesPath.join('.')}" object.`,
path: ['security', index, securityKey],
});
}
}
}
}
for (const { path, operation, value } of getAllOperations(paths)) {
if (!isObject(value)) continue;
const { security } = value;
if (!Array.isArray(security)) {
continue;
}
for (const [index, value] of security.entries()) {
if (!isObject(value)) {
continue;
}
const securityKeys = Object.keys(value);
for (const securityKey of securityKeys) {
if (!allDefs.includes(securityKey)) {
results.push({
message: `Operation "security" values must match a scheme defined in the "${schemesPath.join(
'.',
)}" object.`,
path: ['paths', path, operation, 'security', index, securityKey],
});
}
}
}
}
return results;
},
);