UNPKG

docschema

Version:

Schema declaration and validation library using JsDoc comments

614 lines (518 loc) 15 kB
import { checkResult } from '../checkResult.js' import { objectStringify } from '../functions/utils.js' const regexesForString = { email: /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/ui, cuid: /^c[^\s-]{8,}$/ui, cuid2: /^[a-z][a-z0-9]*$/u, ulid: /^[0-9A-HJKMNP-TV-Z]{26}$/u, uuid: /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/ui, ipv4: /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/u, ipv6: /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/u } /** * @type {Record<keyof checkers, string>} */ const errorPrefixes = { array: 'Expected array ', number: 'Expected number ', string: 'Expected string ' } /** * @param {keyof regexesForString} filterName * @param {string} value * @param {[boolean, string]} tupleFilter * @returns {boolean} */ function applyRegexCheck(filterName, value, tupleFilter) { const [ toBe, errorMessage ] = tupleFilter if (toBe === true && !regexesForString[filterName].test(value)) { setError( errorMessage || `${errorPrefixes.string}"${value}" to be ${filterName}`, filterName, tupleFilter[0] ) return false } if (toBe === false && regexesForString[filterName].test(value)) { setError( errorMessage || `${errorPrefixes.string}"${value}" to not be ${filterName}`, filterName, tupleFilter[0] ) return false } return true } /** * @param {string} message * @param {keyof Filters} filterName * @param {boolean | number | string | RegExp} filterValue */ function setError(message, filterName, filterValue) { checkResult.message = message checkResult.kind = 'filter' checkResult.filter = { name: filterName, value: filterValue } } export const checkers = { /** * @type {{ * min: function(any[], [number, string]): boolean, * max: function(any[], [number, string]): boolean, * length: function(any[], [number, string]): boolean, * }} */ array: { min: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.length < toBe) { const arrayStr = objectStringify(value) setError( customErrorMessage || `${errorPrefixes.array}"${arrayStr}" to have ${toBe} or more elements`, 'min', toBe ) return false } return true }, max: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.length > toBe) { const arrayStr = objectStringify(value) setError( customErrorMessage || `${errorPrefixes.array}"${arrayStr}" to have ${toBe} or less elements`, 'max', toBe ) return false } return true }, length: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.length !== toBe) { const arrayStr = objectStringify(value) setError( customErrorMessage || `${errorPrefixes.array}"${arrayStr}" to have exactly ${toBe} elements`, 'length', toBe ) return false } return true } }, /** * @type {{ * min: function(number, [number, string]): boolean, * max: function(number, [number, string]): boolean, * gte: function(number, [number, string]): boolean, * lte: function(number, [number, string]): boolean, * gt: function(number, [number, string]): boolean, * lt: function(number, [number, string]): boolean, * step: function(number, [number, string]): boolean, * int: function(number, [boolean, string]): boolean, * finite: function(number, [boolean, string]): boolean, * safeInt: function(number, [boolean, string]): boolean, * }} */ number: { // gte alias min: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value < toBe) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be ${toBe} or higher`, 'min', toBe ) return false } return true }, // lte alias max: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value > toBe) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be ${toBe} or lower`, 'max', toBe ) return false } return true }, gte: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value < toBe) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be ${toBe} or higher`, 'gte', toBe ) return false } return true }, lte: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value > toBe) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be ${toBe} or lower`, 'lte', toBe ) return false } return true }, gt: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value <= toBe) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be higher than ${toBe}`, 'gt', toBe ) return false } return true }, lt: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value >= toBe) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be lower than ${toBe}`, 'lt', toBe ) return false } return true }, step: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if ((value % toBe !== 0)) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be multiple of ${toBe}`, 'step', toBe ) return false } return true }, int: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter const isInteger = Number.isInteger(value) if (toBe === true && !isInteger) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be integer`, 'int', toBe ) return false } if (toBe === false && isInteger) { setError( customErrorMessage || `${errorPrefixes.number}${value} not to be integer`, 'int', toBe ) return false } return true }, finite: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter const isFinite = Number.isFinite(value) if (toBe === true && !isFinite) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be finite`, 'finite', toBe ) return false } if (toBe === false && isFinite) { setError( customErrorMessage || `${errorPrefixes.number}${value} not to be finite`, 'finite', toBe ) return false } return true }, safeInt: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (!Number.isInteger(value)) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be integer`, 'safeInt', toBe ) return false } if ( toBe === true && (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) ) { setError( customErrorMessage || `${errorPrefixes.number}${value} to be safe`, 'safeInt', toBe ) return false } if ( toBe === false && ( value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER ) ) { setError( customErrorMessage || `${errorPrefixes.number}${value} not to be safe`, 'safeInt', toBe ) return false } return true } }, /** * @type {{ * min: function(string, [number, string]): boolean, * max: function(string, [number, string]): boolean, * length: function(string, [number, string]): boolean, * startsWith: function(string, [string, string]): boolean, * endsWith: function(string, [string, string]): boolean, * includes: function(string, [string, string]): boolean, * excludes: function(string, [string, string]): boolean, * url: function(string, [boolean, string]): boolean, * pattern: function(string, [RegExp, string]): boolean, * ip: function(string, [boolean, string]): boolean, * email: function(string, [boolean, string]): boolean, * cuid: function(string, [boolean, string]): boolean, * cuid2: function(string, [boolean, string]): boolean, * ulid: function(string, [boolean, string]): boolean, * uuid: function(string, [boolean, string]): boolean, * ipv4: function(string, [boolean, string]): boolean, * ipv6: function(string, [boolean, string]): boolean, * }} */ string: { min: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.length < toBe) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to be at least ${toBe} characters long`, 'min', toBe ) return false } return true }, max: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.length > toBe) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to be max ${toBe} characters long`, 'max', toBe ) return false } return true }, length: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.length !== toBe) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to be exactly ${toBe} characters long`, 'length', toBe ) return false } return true }, startsWith: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (!value.startsWith(toBe)) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to start with "${toBe}"`, 'startsWith', toBe ) return false } return true }, endsWith: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (!value.endsWith(toBe)) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to end with "${toBe}"`, 'endsWith', toBe ) return false } return true }, includes: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (!value.includes(toBe)) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to include "${toBe}"`, 'endsWith', toBe ) return false } return true }, excludes: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (value.includes(toBe)) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" not to include "${toBe}"`, 'endsWith', toBe ) return false } return true }, url: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (toBe === true) { try { const url = new URL(value) } catch { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to be URL`, 'url', toBe ) return false } } if (toBe === false) { try { const url = new URL(value) } catch { return true } setError( customErrorMessage || `${errorPrefixes.string}"${value}" not to be URL`, 'url', toBe ) return false } return true }, pattern: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if (!toBe.test(value)) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to respect the regex pattern ${toBe}`, 'pattern', toBe ) return false } return true }, ip: (value, tupleFilter) => { const [ toBe, customErrorMessage ] = tupleFilter if ( toBe === true && ( !regexesForString.ipv4.test(value) && !regexesForString.ipv6.test(value) ) ) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to be IP address`, 'ip', toBe ) return false } if ( toBe === false && ( regexesForString.ipv4.test(value) || regexesForString.ipv6.test(value) ) ) { setError( customErrorMessage || `${errorPrefixes.string}"${value}" to not be IP address`, 'ip', toBe ) return false } return true }, email: (value, tupleFilter) => { return applyRegexCheck('email', value, tupleFilter) }, cuid: (value, tupleFilter) => { return applyRegexCheck('cuid', value, tupleFilter) }, cuid2: (value, tupleFilter) => { return applyRegexCheck('cuid2', value, tupleFilter) }, ulid: (value, tupleFilter) => { return applyRegexCheck('ulid', value, tupleFilter) }, uuid: (value, tupleFilter) => { return applyRegexCheck('uuid', value, tupleFilter) }, ipv4: (value, tupleFilter) => { return applyRegexCheck('ipv4', value, tupleFilter) }, ipv6: (value, tupleFilter) => { return applyRegexCheck('ipv6', value, tupleFilter) } } }