import {ValidationResult, ValidatorFunction, ValidatorFunctionParams} from './types' import {isJSON} from '../../util' /** * String-related validation rules. */ const regexes: {[key: string]: RegExp} = { 'string.is.alpha': /[a-zA-Z]*/, 'string.is.alpha_num': /[a-zA-Z0-9]*/, 'string.is.alpha_dash': /[a-zA-Z-]*/, 'string.is.alpha_score': /[a-zA-Z_]*/, 'string.is.alpha_num_dash_score': /[a-zA-Z\-_0-9]*/, 'string.is.email': /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])/, // eslint-disable-line no-control-regex 'string.is.ip': /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/, 'string.is.ip.v4': /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/, 'string.is.ip.v6': /(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))/, 'string.is.mime': /^(?=[-a-z]{1,127}\/[-.a-z0-9]{1,127}$)[a-z]+(-[a-z]+)*\/[a-z0-9]+([-.][a-z0-9]+)*$/, 'string.is.url': /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w\-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/, 'string.is.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}$/, } function validateRex(key: string, inputValue: unknown, message: string): ValidationResult { if ( regexes[key].test(String(inputValue)) ) { return { valid: true } } return { valid: false, message, } } /** Requires the input value to be alphabetical characters only. */ function alpha(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.alpha', inputValue, 'must be alphabetical only') } /** Requires the input value to be alphanumeric characters only. */ function alphaNum(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.alpha_num', inputValue, 'must be alphanumeric only') } /** Requires the input value to be alphabetical characters or the "-" character only. */ function alphaDash(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.alpha_dash', inputValue, 'must be alphabetical and dashes only') } /** Requires the input value to be alphabetical characters or the "_" character only. */ function alphaScore(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.alpha_score', inputValue, 'must be alphabetical and underscores only') } /** Requires the input value to be alphabetical characters, numeric characters, "-", or "_" only. */ function alphaNumDashScore(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.alpha_num_dash_score', inputValue, 'must be alphanumeric, dashes, and underscores only') } /** Requires the input value to be a valid RFC email address format. */ function email(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.email', inputValue, 'must be an email address') } /** Requires the input value to be a valid IPv4 or IPv6 address. */ function ip(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.ip', inputValue, 'must be a valid IP address') } /** Requires the input value to be a valid IPv4 address. */ function ipv4(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.ip.v4', inputValue, 'must be a valid IP version 4 address') } /** Requires the input value to be a valid IPv6 address. */ function ipv6(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.ip.v6', inputValue, 'must be a valid IP version 6 address') } /** Requires the input value to be a valid file MIME type. */ function mime(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.mime', inputValue, 'must be a valid MIME-type') } /** Requires the input value to be a valid RFC URL format. */ function url(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.url', inputValue, 'must be a valid URL') } /** Requires the input value to be a valid RFC UUID format. */ function uuid(fieldName: string, inputValue: unknown): ValidationResult { return validateRex('string.is.uuid', inputValue, 'must be a valid UUID') } /** * Builds a validation function that requires the input value to match the given regex. * @param rex */ function regex(rex: RegExp): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( rex.test(String(inputValue)) ) { return { valid: true } } return { valid: false, message: 'is not valid', } } } /** * Builds a validation function that requires the input to NOT match the given regex. * @param rex */ function notRegex(rex: RegExp): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( !rex.test(String(inputValue)) ) { return { valid: true } } return { valid: false, message: 'is not valid', } } } /** * Builds a validation function that requires the given input to end with the substring. * @param substr */ function ends(substr: string): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( String(inputValue).endsWith(substr) ) { return { valid: true } } return { valid: false, message: `must end with "${substr}"`, } } } /** * Builds a validation function that requires the given input to begin with the substring. * @param substr */ function begins(substr: string): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( String(inputValue).startsWith(substr) ) { return { valid: true } } return { valid: false, message: `must begin with "${substr}"`, } } } /** Requires the input value to be a valid JSON string. */ function json(fieldName: string, inputValue: unknown): ValidationResult { if ( isJSON(String(inputValue)) ) { return { valid: true } } return { valid: false, message: 'must be valid JSON', } } /** * Builds a validator function that requires the input value to have exactly len many characters. * @param len */ function length(len: number): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( String(inputValue).length === len ) { return { valid: true } } return { valid: false, message: `must be exactly of length ${len}`, } } } /** * Builds a validator function that requires the input value to have at least len many characters. * @param len */ function lengthMin(len: number): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( String(inputValue).length >= len ) { return { valid: true } } return { valid: false, message: `must be at least length ${len}`, } } } /** * Builds a validator function that requires the input value to have at most len many characters. * @param len */ function lengthMax(len: number): ValidatorFunction { return (fieldName: string, inputValue: unknown): ValidationResult => { if ( String(inputValue).length <= len ) { return { valid: true } } return { valid: false, message: `must be at most length ${len}`, } } } /** * Validator function that requires the input value to match a `${field}Confirm` field's value. * @param fieldName * @param inputValue * @param params */ function confirmed(fieldName: string, inputValue: unknown, params: ValidatorFunctionParams): ValidationResult { const confirmedFieldName = `${fieldName}Confirm` if ( inputValue === params.data[confirmedFieldName] ) { return { valid: true } } return { valid: false, message: `confirmation does not match`, } } export const Str = { alpha, alphaNum, alphaDash, alphaScore, alphaNumDashScore, email, ip, ipv4, ipv6, mime, url, uuid, regex, notRegex, ends, begins, json, length, lengthMin, lengthMax, confirmed, }