UNPKG

swagger-route-validator

Version:

The fastest openapi validation middleware

266 lines (265 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.set = set; exports.scanRef = scanRef; exports.validateValue = validateValue; exports.type = type; const format_1 = require("./format"); const error_1 = require("./error"); const propertyMap = { format: format_1.format, pattern, enum: enumeration, minimum, maximum, exclusiveMinimum, exclusiveMaximum, maxItems: () => { }, minItems: () => { }, maxProperties: () => { }, minProperties: () => { }, minLength, maxLength, multipleOf, $not: isNot, $allOf: isAllOf, $anyOf: isAnyOf, $oneOf: isOneOf, }; function set(req) { return (cursor, value) => { const keys = cursor.split('.'); const last = keys.pop(); if (keys[0] === 'path') return; // Fix for req.path in Express 5 keys.reduce((o, k) => (o[k] || {}), req)[last] = value; }; } function scanRef(path, spec) { // Check if the path is for an external reference const target = (path[0] !== '#') ? require(path.substring(0, path.indexOf('#') || path.length)) : spec; const pathTokens = path.substring(path.indexOf('#') + 1).split('/'); return pathTokens.reduce((cursor, token) => token !== '' ? cursor?.[token] || null : cursor, target); } function validateValue(cursor, value, spec, setDefault, errors, fullSpec = {}) { if (value !== undefined && value !== null) { if (spec.schema) { Object.assign(spec, spec.schema); if (!spec.type) spec.type = 'object'; } if (spec.$ref) { const refName = spec.$ref; const refKey = refName.split('/').slice(-1)[0]; spec = scanRef(refName, fullSpec); if (!spec) { return errors.push((0, error_1.makeError)(cursor, value, `Could not find definition for ${refName}`)); } cursor += `:${refKey}`; } type(cursor, value, spec.type, spec, setDefault, errors); for (const prop in propertyMap) { if (spec[prop] !== undefined) { propertyMap[prop](cursor, value, spec[prop], errors); } } } else { if (spec.required === true || (spec.required === undefined && spec.default === undefined && spec.nullable != true)) { errors.push((0, error_1.makeError)(cursor, value, `Value for ${spec.name} is required and was not provided`)); } if (spec.default !== undefined) { setDefault(cursor, spec.default); } } return errors; } // -- Atoms function pattern(cursor, value, expectation, errors) { const result = new RegExp(expectation).exec(value); if (result === null) { errors.push((0, error_1.makeError)(cursor, value, `Value does not match pattern ${expectation}`)); } } function minimum(cursor, value, expectation, errors) { if (value < expectation) { errors.push((0, error_1.makeError)(cursor, value, `Value is smaller than minimum: ${expectation}`)); } } function maximum(cursor, value, expectation, errors) { if (value > expectation) { errors.push((0, error_1.makeError)(cursor, value, `Value is larger than maximum: ${expectation}`)); } } function exclusiveMinimum(cursor, value, expectation, errors) { if (value <= expectation) { errors.push((0, error_1.makeError)(cursor, value, `Value is smaller or equal the minimum: ${expectation}`)); } } function exclusiveMaximum(cursor, value, expectation, errors) { if (value >= expectation) { errors.push((0, error_1.makeError)(cursor, value, `Value is larger or equal the maximum: ${expectation}`)); } } function enumeration(cursor, value, expectation, errors) { if (!expectation.includes(value)) { errors.push((0, error_1.makeError)(cursor, value, `Value is not one of: [${expectation.toString()}]`)); } } function minLength(cursor, value, expectation, errors) { if (value.length < expectation) { errors.push((0, error_1.makeError)(cursor, value, `Value has does not match minimum length: ${expectation}`)); } } function maxLength(cursor, value, expectation, errors) { if (value.length > expectation) { errors.push((0, error_1.makeError)(cursor, value, `Value has does not match maximum length: ${expectation}`)); } } function multipleOf(cursor, value, expectation, errors) { if (value % expectation !== 0) { errors.push((0, error_1.makeError)(cursor, value, `Value is not a multiple of: ${expectation}`)); } } function isNot(cursor, value, expectation, errors) { const { items, numErrors } = runAssertions(value, expectation); if (numErrors.length !== items.length) { errors.push((0, error_1.makeError)(cursor, value, `Value was found in $not statement ${expectation}`)); } } function isAnyOf(cursor, value, expectation, errors) { const { items, numErrors } = runAssertions(value, expectation); if (items.length - numErrors.length < 1) { errors.push((0, error_1.makeError)(cursor, value, `Value did not match $anyOf statement ${expectation}`)); } } function isAllOf(cursor, value, expectation, errors) { const { numErrors } = runAssertions(value, expectation); if (numErrors.length !== 0) { errors.push((0, error_1.makeError)(cursor, value, `Value did not match $allOf statement ${expectation}`)); } } function isOneOf(cursor, value, expectation, errors) { const { items, numErrors } = runAssertions(value, expectation); if (items.length - numErrors.length !== 1) { errors.push((0, error_1.makeError)(cursor, value, `Value did not match $oneOf statement ${expectation}`)); } } function runAssertions(value, expectation) { const items = (Array.isArray(expectation) ? expectation : [expectation]); const numErrors = items.map(item => validateValue('', value, item, () => { }, []).length > 0).filter(item => !!item); // Length = number of errors return { items, numErrors }; } function type(cursor, value, expectation, spec, setDefault, errors) { switch (expectation) { case 'string': string(cursor, value, setDefault, errors); break; case 'integer': integer(cursor, value, setDefault, errors); break; case 'number': number(cursor, value, setDefault, errors); break; case 'boolean': boolean(cursor, value, setDefault, errors); break; case 'array': list(cursor, value, spec, setDefault, errors); break; case 'list': list(cursor, value, spec, setDefault, errors); break; case 'object': object(cursor, value, spec, setDefault, errors); break; case 'schema': object(cursor, value, spec, setDefault, errors); break; case 'any': break; default: throw new Error(`Invalid field type value ${expectation} in ${JSON.stringify(spec)} at ${cursor}`); } return errors; } // data types function string(cursor, value, setDefault, errors) { if (typeof value !== 'string') { errors.push((0, error_1.makeError)(cursor, value, 'Value is not a string')); } else { setDefault(cursor, value); } } function number(cursor, value, setDefault, errors) { const parsedValue = Number(value); if (typeof parsedValue !== 'number' || Number.isNaN(parsedValue)) { errors.push((0, error_1.makeError)(cursor, value, 'Value is not a number')); } else { setDefault(cursor, parsedValue); } } function integer(cursor, value, setDefault, errors) { const parsedValue = Number(value); if (!Number.isInteger(parsedValue)) { errors.push((0, error_1.makeError)(cursor, value, 'Value is not an integer')); } else { setDefault(cursor, parsedValue); } } function boolean(cursor, value, setDefault, errors) { if (value !== true && value !== false && value !== 'true' && value !== 'false') { errors.push((0, error_1.makeError)(cursor, value, 'Value is not a boolean')); } else { setDefault(cursor, value === 'true'); } } function list(cursor, value, spec, setDefault, errors) { if (typeof value === 'string') value = value.split(','); if (!Array.isArray(value)) return errors.push((0, error_1.makeError)(cursor, value, 'Value is not an array')); if (spec.minItems !== undefined && value.length < spec.minItems) return errors.push((0, error_1.makeError)(cursor, value, `Value does not meet the minimum number of items ${spec.minItems}`)); if (spec.maxItems !== undefined && value.length > spec.maxItems) return errors.push((0, error_1.makeError)(cursor, value, `Value exceeds the maximum number of items ${spec.maxItems}`)); const param = spec.items || { type: 'any' }; for (let i = 0; i < value.length; i++) { const newCursor = `${cursor}[${i}]`; param.name = 'items'; validateValue(newCursor, value[i], param, setDefault, errors); } } function object(cursor, value, spec, setDefault, errors) { if (typeof value !== 'object' || Array.isArray(value)) return errors.push((0, error_1.makeError)(cursor, value, 'Value is not an object')); let count = 0; for (let key in value) ++count; if (spec.maxProperties !== null && count > spec.maxProperties) return errors.push((0, error_1.makeError)(cursor, value, `Value has more properties than maximum: ${spec.maxProperties}`)); if (spec.minProperties !== null && count < spec.minProperties) return errors.push((0, error_1.makeError)(cursor, value, `Value has fewer properties than minimum: ${spec.minProperties}`)); if (spec.required !== undefined && spec.required.length) { const missingKeys = spec.required.filter(key => !(key in value)); if (missingKeys.length > 0) { return errors.push((0, error_1.makeError)(cursor, value, `Value is missing required keys [${missingKeys.toString()}]`)); } } if (spec.additionalProperties === false) { for (const prop in value) { if (!(prop in spec.properties)) return errors.push((0, error_1.makeError)(cursor, value, `Unexpected key ${prop} in object, additionalProperties not permitted`)); } } for (const prop in spec.properties) { const param = spec.properties[prop]; const newCursor = `${cursor}.${prop}`; param.name = prop; validateValue(newCursor, value[prop], param, setDefault, errors); } }