Skip to content

Instantly share code, notes, and snippets.

@doasync
Last active May 26, 2020 11:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doasync/a7a412ce7ee96f70796fdf0f5cf9f346 to your computer and use it in GitHub Desktop.
Save doasync/a7a412ce7ee96f70796fdf0f5cf9f346 to your computer and use it in GitHub Desktop.
Final form validators
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]);
export { makeOptional } from './make-optional';
export { setErrorMap, getMessage, defaultErrorMap } from './error-map';
export * from './validators';
const { isArray } = Array;
// prettier-ignore
export const makeOptional = validator => value => (
value == null || (isArray(value) && value.length === 0)
? undefined
: validator(value)
);
/**
* 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