UNPKG

@redocly/openapi-core

Version:

See https://github.com/Redocly/redocly-cli

265 lines 10.2 kB
import { isPlainObject, isString as runOnValue, isTruthy } from '../../../utils.js'; import { isOrdered, getIntersectionLength, regexFromString } from './utils.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