Last active
May 26, 2020 11:14
-
-
Save doasync/a7a412ce7ee96f70796fdf0f5cf9f346 to your computer and use it in GitHub Desktop.
Final form validators
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
notEmpty, | |
booleanType, | |
numberType, | |
numberMin, | |
numberMax, | |
numberGt, | |
numberLt, | |
numberBetween, | |
integerType, | |
integerMin, | |
integerMax, | |
integerGt, | |
integerLt, | |
integerBetween, | |
stringType, | |
stringMin, | |
stringMax, | |
stringBetween, | |
arrayType, | |
emailType, | |
dateType, | |
minNumbers, | |
minChars, | |
} from './validators'; | |
// TODO: Bind errorMap to the instance of a form (do not use global) | |
const errorMap = new Map([ | |
[notEmpty, 'Required'], | |
[booleanType, 'Must be a boolean'], | |
[numberType, 'Must be a number'], | |
[numberMin, min => `Min value is ${min}`], | |
[numberMax, max => `Max value is ${max}`], | |
[numberGt, min => `Value must be greater than ${min}`], | |
[numberLt, max => `Value must be less than ${max}`], | |
[numberBetween, (min, max) => `Value must be between ${min} and ${max}`], | |
[integerType, 'Must be an integer number'], | |
[integerMin, min => `Min value is ${min}`], | |
[integerMax, max => `Max value is ${max}`], | |
[integerGt, min => `Value must be greater than ${min}`], | |
[integerLt, max => `Value must be less than ${max}`], | |
[integerBetween, (min, max) => `Value must be between ${min} and ${max}`], | |
[stringType, 'Must be a string'], | |
[stringMin, min => `Min length is ${min}`], | |
[stringMax, max => `Max length is ${max}`], | |
[stringBetween, (min, max) => `Length must be between ${min} and ${max}`], | |
[arrayType, 'Must be an array'], | |
[emailType, 'Must be a valid email'], | |
[dateType, 'Must be a valid date'], | |
[minNumbers, min => `Must contains at least ${min} number${min > 1 ? 's' : ''}`], | |
[minChars, min => `Must contains at least ${min} char${min > 1 ? 's' : ''}`], | |
]); | |
const errorMapRef = { | |
current: errorMap, | |
}; | |
export const setErrorMap = (customErrorMap) => { | |
errorMapRef.current = customErrorMap; | |
}; | |
export const getMessage = (validator, ...args) => { | |
let message = errorMapRef.current.get(validator); | |
if (message === undefined) { | |
throw new Error('An empty message returned from validation errorMap'); | |
} | |
if (typeof message === 'function') { | |
message = message(...args); | |
} | |
return message; | |
}; | |
export const defaultErrorMap = new Map([...errorMap]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export { makeOptional } from './make-optional'; | |
export { setErrorMap, getMessage, defaultErrorMap } from './error-map'; | |
export * from './validators'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { isArray } = Array; | |
// prettier-ignore | |
export const makeOptional = validator => value => ( | |
value == null || (isArray(value) && value.length === 0) | |
? undefined | |
: validator(value) | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Validators (return undefined or string) | |
* | |
* A validator must return an error message if a value is omitted | |
*/ | |
import { getMessage } from './error-map'; | |
export const notEmpty = value => (value !== null && value !== undefined && value !== '' | |
? undefined | |
: getMessage(notEmpty)); | |
export const booleanType = value => notEmpty(value) | |
|| (typeof value === 'boolean' ? undefined : getMessage(booleanType)); | |
export const numberType = (value) => { | |
const msgNotEmpty = notEmpty(value); | |
const msgNumberType = getMessage(numberType); | |
if (msgNotEmpty) { | |
return msgNotEmpty; | |
} | |
if (String(value).charAt(0) === '.' || String(value).slice(-1) === '.') { | |
return msgNumberType; | |
} | |
const numberValue = Number(value); | |
return !Number.isNaN(numberValue) && Number.isFinite(numberValue) | |
? undefined | |
: msgNumberType; | |
}; | |
export const numberMin = min => value => numberType(value) | |
|| (Number(value) >= min ? undefined : getMessage(numberMin, min)); | |
export const numberMax = max => value => numberType(value) | |
|| (Number(value) <= max ? undefined : getMessage(numberMax, max)); | |
export const numberGt = min => value => numberType(value) | |
|| (Number(value) > min ? undefined : getMessage(numberGt, min)); | |
export const numberLt = max => value => numberType(value) | |
|| (Number(value) < max ? undefined : getMessage(numberLt, max)); | |
export const numberBetween = (min, max) => value => numberType(value) | |
|| (Number(value) >= min && Number(value) <= max | |
? undefined | |
: getMessage(numberBetween, min, max)); | |
export const integerType = value => numberType(value) | |
|| (Number.isInteger(Number(value)) ? undefined : getMessage(integerType)); | |
export const integerMin = min => value => integerType(value) | |
|| (Number(value) >= min ? undefined : getMessage(integerMin, min)); | |
export const integerMax = max => value => integerType(value) | |
|| (Number(value) <= max ? undefined : getMessage(integerMax, max)); | |
export const integerGt = min => value => integerType(value) | |
|| (Number(value) > min ? undefined : getMessage(integerGt, min)); | |
export const integerLt = max => value => integerType(value) | |
|| (Number(value) < max ? undefined : getMessage(integerLt, max)); | |
export const integerBetween = (min, max) => value => integerType(value) | |
|| (Number(value) >= min && Number(value) <= max | |
? undefined | |
: getMessage(integerBetween, min, max)); | |
export const stringType = value => notEmpty(value) | |
|| (typeof value === 'string' ? undefined : getMessage(stringType)); | |
export const stringMin = min => value => stringType(value) | |
|| (value.length >= min ? undefined : getMessage(stringMin, min)); | |
export const stringMax = max => value => stringType(value) | |
|| (value.length <= max ? undefined : getMessage(stringMax, max)); | |
export const stringBetween = (min, max) => value => stringType(value) | |
|| (value.length >= min && value.length <= max | |
? undefined | |
: getMessage(stringBetween, min, max)); | |
export const arrayType = value => (Array.isArray(value) | |
? notEmpty(value[0]) | |
: notEmpty(value) || getMessage(arrayType)); | |
/* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email#Basic_validation */ | |
const emailPattern = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; | |
export const emailType = value => stringMax(254)(value) | |
|| (emailPattern.test(value.trim()) ? undefined : getMessage(emailType)); | |
export const emailMax = max => value => emailType(value) || stringMax(max)(value); | |
export const dateType = value => notEmpty(value) | |
|| (typeof value === 'object' | |
&& Object.prototype.toString.call(value) === '[object Date]' | |
&& !Number.isNaN(value.getTime()) | |
? undefined | |
: getMessage(dateType)); | |
export const minNumbers = min => value => (typeof value === 'string' && (value.match(/\d/g) || []).length >= min | |
? undefined | |
: getMessage(minNumbers, min)); | |
export const minChars = min => value => (typeof value === 'string' && (value.match(/[a-zA-Z]/g) || []).length >= min | |
? undefined | |
: getMessage(minChars, min)); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment