@redocly/openapi-core
Version:
See https://github.com/Redocly/redocly-cli
269 lines • 10.5 kB
JavaScript
import { isPlainObject } from '../../../utils/is-plain-object.js';
import { isString as runOnValue } from '../../../utils/is-string.js';
import { isTruthy } from '../../../utils/is-truthy.js';
import { getIntersectionLength } from '../../../utils/get-intersection-length.js';
import { isOrdered } from '../../../utils/is-ordered.js';
import { regexFromString } from '../../../utils/regex-from-string.js';
export const runOnKeysSet = new Set([
'mutuallyExclusive',
'mutuallyRequired',
'enum',
'pattern',
'notPattern',
'minLength',
'maxLength',
'casing',
'sortOrder',
'disallowed',
'required',
'requireAny',
'ref',
'const',
'defined', // In case if `property` for assertions is not added
]);
export const runOnValuesSet = new Set([
'pattern',
'notPattern',
'enum',
'defined',
'nonEmpty',
'minLength',
'maxLength',
'casing',
'sortOrder',
'ref',
'const',
]);
export const asserts = {
pattern: (value, condition, { baseLocation, rawValue }) => {
if (typeof value === 'undefined' || isPlainObject(value))
return []; // property doesn't exist or is an object, no need to lint it with this assert
const values = Array.isArray(value) ? value : [value];
const regex = regexFromString(condition);
return values
.map((_val) => !regex?.test(_val) && {
message: `"${_val}" should match a regex ${condition}`,
location: runOnValue(value)
? baseLocation
: isPlainObject(rawValue)
? baseLocation.child(_val).key()
: baseLocation.key(),
})
.filter(isTruthy);
},
notPattern: (value, condition, { baseLocation, rawValue }) => {
if (typeof value === 'undefined' || isPlainObject(value))
return []; // property doesn't exist or is an object, no need to lint it with this assert
const values = Array.isArray(value) ? value : [value];
const regex = regexFromString(condition);
return values
.map((_val) => regex?.test(_val) && {
message: `"${_val}" should not match a regex ${condition}`,
location: runOnValue(value)
? baseLocation
: isPlainObject(rawValue)
? baseLocation.child(_val).key()
: baseLocation.key(),
})
.filter(isTruthy);
},
enum: (value, condition, { baseLocation }) => {
if (typeof value === 'undefined' || isPlainObject(value))
return []; // property doesn't exist or is an object, no need to lint it with this assert
const values = Array.isArray(value) ? value : [value];
return values
.map((_val) => !condition.includes(_val) && {
message: `"${_val}" should be one of the predefined values`,
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
})
.filter(isTruthy);
},
defined: (value, condition = true, { baseLocation }) => {
const isDefined = typeof value !== 'undefined';
const isValid = condition ? isDefined : !isDefined;
return isValid
? []
: [
{
message: condition ? `Should be defined` : 'Should be not defined',
location: baseLocation,
},
];
},
required: (value, keys, { baseLocation }) => {
return keys
.map((requiredKey) => !value.includes(requiredKey) && {
message: `${requiredKey} is required`,
location: baseLocation.key(),
})
.filter(isTruthy);
},
disallowed: (value, condition, { baseLocation }) => {
if (typeof value === 'undefined' || isPlainObject(value))
return []; // property doesn't exist or is an object, no need to lint it with this assert
const values = Array.isArray(value) ? value : [value];
return values
.map((_val) => condition.includes(_val) && {
message: `"${_val}" is disallowed`,
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
})
.filter(isTruthy);
},
const: (value, condition, { baseLocation }) => {
if (typeof value === 'undefined')
return [];
if (Array.isArray(value)) {
return value
.map((_val) => condition !== _val && {
message: `"${_val}" should be equal ${condition} `,
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
})
.filter(isTruthy);
}
else {
return value !== condition
? [
{
message: `${value} should be equal ${condition}`,
location: baseLocation,
},
]
: [];
}
},
nonEmpty: (value, condition = true, { baseLocation }) => {
const isEmpty = typeof value === 'undefined' || value === null || value === '';
const isValid = condition ? !isEmpty : isEmpty;
return isValid
? []
: [
{
message: condition ? `Should not be empty` : 'Should be empty',
location: baseLocation,
},
];
},
minLength: (value, condition, { baseLocation }) => {
if (typeof value === 'undefined' || value.length >= condition)
return []; // property doesn't exist, no need to lint it with this assert
return [
{
message: `Should have at least ${condition} characters`,
location: baseLocation,
},
];
},
maxLength: (value, condition, { baseLocation }) => {
if (typeof value === 'undefined' || value.length <= condition)
return []; // property doesn't exist, no need to lint it with this assert
return [
{
message: `Should have at most ${condition} characters`,
location: baseLocation,
},
];
},
casing: (value, condition, { baseLocation }) => {
if (typeof value === 'undefined' || isPlainObject(value))
return []; // property doesn't exist or is an object, no need to lint it with this assert
const values = Array.isArray(value) ? value : [value];
const casingRegexes = {
camelCase: /^[a-z][a-zA-Z0-9]*$/g,
'kebab-case': /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/g,
snake_case: /^([a-z][a-z0-9]*)(_[a-z0-9]+)*$/g,
PascalCase: /^[A-Z][a-zA-Z0-9]+$/g,
MACRO_CASE: /^([A-Z][A-Z0-9]*)(_[A-Z0-9]+)*$/g,
'COBOL-CASE': /^([A-Z][A-Z0-9]*)(-[A-Z0-9]+)*$/g,
flatcase: /^[a-z][a-z0-9]+$/g,
};
return values
.map((_val) => !_val.match(casingRegexes[condition]) && {
message: `"${_val}" should use ${condition}`,
location: runOnValue(value) ? baseLocation : baseLocation.child(_val).key(),
})
.filter(isTruthy);
},
sortOrder: (value, condition, { baseLocation }) => {
const direction = condition.direction || condition;
const property = condition.property;
if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && !property) {
return [
{
message: `Please define a property to sort objects by`,
location: baseLocation,
},
];
}
if (typeof value === 'undefined' || isOrdered(value, condition))
return [];
return [
{
message: `Should be sorted in ${direction === 'asc' ? 'an ascending' : 'a descending'} order${property ? ` by property ${property}` : ''}`,
location: baseLocation,
},
];
},
mutuallyExclusive: (value, condition, { baseLocation }) => {
if (getIntersectionLength(value, condition) < 2)
return [];
return [
{
message: `${condition.join(', ')} keys should be mutually exclusive`,
location: baseLocation.key(),
},
];
},
mutuallyRequired: (value, condition, { baseLocation }) => {
const isValid = getIntersectionLength(value, condition) > 0
? getIntersectionLength(value, condition) === condition.length
: true;
return isValid
? []
: [
{
message: `Properties ${condition.join(', ')} are mutually required`,
location: baseLocation.key(),
},
];
},
requireAny: (value, condition, { baseLocation }) => {
return getIntersectionLength(value, condition) >= 1
? []
: [
{
message: `Should have any of ${condition.join(', ')}`,
location: baseLocation.key(),
},
];
},
ref: (_value, condition, { baseLocation, rawValue }) => {
if (typeof rawValue === 'undefined')
return []; // property doesn't exist, no need to lint it with this assert
const hasRef = rawValue.hasOwnProperty('$ref');
if (typeof condition === 'boolean') {
const isValid = condition ? hasRef : !hasRef;
return isValid
? []
: [
{
message: condition ? `should use $ref` : 'should not use $ref',
location: hasRef ? baseLocation : baseLocation.key(),
},
];
}
const regex = regexFromString(condition);
const isValid = hasRef && regex?.test(rawValue['$ref']);
return isValid
? []
: [
{
message: `$ref value should match ${condition}`,
location: hasRef ? baseLocation : baseLocation.key(),
},
];
},
};
export function buildAssertCustomFunction(fn) {
return (value, options, ctx) => fn.call(null, value, options, ctx);
}
//# sourceMappingURL=asserts.js.map