Created
August 21, 2019 05:50
-
-
Save xamedow/f8638919096a9db74e038186cdcfcc95 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 { | |
applyValidators, | |
isCyrillic, | |
isEmail, | |
isEntityOgrnValid, | |
isEntityINNValid, | |
isIeOgrnValid, | |
isIEINNValid, | |
required, | |
isPassword, | |
isEqual, | |
isPhone, | |
isNotPhone, | |
isArrayValid | |
} from './validators'; | |
const CORRECT_IE_INNS = { | |
key1: '502906602876', | |
key2: '320500822345', | |
key3: '132808730606', | |
key4: '526006483351' | |
}; | |
const INCORRECT_IE_INNS = { | |
key1: '502906602846', | |
key2: '3205008454', | |
key3: '13280e30606', | |
key4: '483351' | |
}; | |
const CORRECT_ENTITY_OGRN = { | |
key1: '1053600591197', | |
key2: '1026402000657', | |
key3: '5077746887312', | |
key4: '1117847533320', | |
key5: '1026600578916', | |
key6: '1020400750743' | |
}; | |
const INCORRECT_ENTITY_OGRN = { | |
key1: 'abc', | |
key2: '1117847533325', | |
key3: '102660057891615', | |
key4: '102', | |
key5: {} | |
}; | |
const CORRECT_IE_OGRN = { | |
key1: '312502904600034', | |
key2: '308325406500119', | |
key3: '313132804400022', | |
key4: '318246800120852' | |
}; | |
const INCORRECT_IE_OGRN = { | |
key1: 'abc', | |
key2: '15456545', | |
key3: '1026600578945465' | |
}; | |
describe('validation/INN', () => { | |
describe('entity INN', () => { | |
test('correct values', () => { | |
const CORRECT_ENTITY_INNS = { | |
key1: '3664069397', | |
key2: '6449013711', | |
key3: '7714698320', | |
key4: '6602008520', | |
key5: '7714698320', | |
key6: '7744000302' | |
}; | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isEntityINNValid('key1', errorMessage), | |
isEntityINNValid('key2', errorMessage), | |
isEntityINNValid('key3', errorMessage), | |
isEntityINNValid('key4', errorMessage), | |
isEntityINNValid('key5', errorMessage), | |
isEntityINNValid('key6', errorMessage) | |
]; | |
const actual = applyValidators(validations)(CORRECT_ENTITY_INNS); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('incorrect values', () => { | |
const INCORRECT_ENTITY_INNS = { | |
key1: '0411003726', | |
key2: '320500845465', | |
key3: '13280e30606', | |
key4: '483351' | |
}; | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isEntityINNValid('key1', errorMessage), | |
isEntityINNValid('key2', errorMessage), | |
isEntityINNValid('key3', errorMessage), | |
isEntityINNValid('key4', errorMessage) | |
]; | |
const actual = applyValidators(validations)(INCORRECT_ENTITY_INNS); | |
const expected = { | |
key1: errorMessage, | |
key2: errorMessage, | |
key3: errorMessage, | |
key4: errorMessage | |
}; | |
expect(actual).toEqual(expected); | |
}); | |
}); | |
describe('IE INN', () => { | |
test('correct INNs', () => { | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isIEINNValid('key1', errorMessage), | |
isIEINNValid('key2', errorMessage), | |
isIEINNValid('key3', errorMessage), | |
isIEINNValid('key4', errorMessage) | |
]; | |
const actual = applyValidators(validations)(CORRECT_IE_INNS); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('incorrect INNs', () => { | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isIEINNValid('key1', errorMessage), | |
isIEINNValid('key2', errorMessage), | |
isIEINNValid('key3', errorMessage), | |
isIEINNValid('key4', errorMessage) | |
]; | |
const actual = applyValidators(validations)(INCORRECT_IE_INNS); | |
const expected = { | |
key1: errorMessage, | |
key2: errorMessage, | |
key3: errorMessage, | |
key4: errorMessage | |
}; | |
expect(actual).toEqual(expected); | |
}); | |
}); | |
}); | |
describe('validation/OGRN', () => { | |
describe('Entity', () => { | |
test('correct values', () => { | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isEntityOgrnValid('key1', errorMessage), | |
isEntityOgrnValid('key2', errorMessage), | |
isEntityOgrnValid('key3', errorMessage), | |
isEntityOgrnValid('key4', errorMessage), | |
isEntityOgrnValid('key5', errorMessage), | |
isEntityOgrnValid('key6', errorMessage) | |
]; | |
const actual = applyValidators(validations)(CORRECT_ENTITY_OGRN); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('incorrect values', () => { | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isEntityOgrnValid('key1', errorMessage), | |
isEntityOgrnValid('key2', errorMessage), | |
isEntityOgrnValid('key3', errorMessage), | |
isEntityOgrnValid('key4', errorMessage) | |
]; | |
const actual = applyValidators(validations)(INCORRECT_ENTITY_OGRN); | |
const expected = { | |
key1: errorMessage, | |
key2: errorMessage, | |
key3: errorMessage, | |
key4: errorMessage | |
}; | |
expect(actual).toEqual(expected); | |
}); | |
}); | |
describe('IE', () => { | |
test('correct values', () => { | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isIeOgrnValid('key1', errorMessage), | |
isIeOgrnValid('key2', errorMessage), | |
isIeOgrnValid('key3', errorMessage), | |
isIeOgrnValid('key4', errorMessage) | |
]; | |
const actual = applyValidators(validations)(CORRECT_IE_OGRN); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('incorrect values', () => { | |
const errorMessage = 'Value has wrong format'; | |
const validations = [ | |
isIeOgrnValid('key1', errorMessage), | |
isIeOgrnValid('key2', errorMessage), | |
isIeOgrnValid('key3', errorMessage) | |
]; | |
const actual = applyValidators(validations)(INCORRECT_IE_OGRN); | |
const expected = { | |
key1: errorMessage, | |
key2: errorMessage, | |
key3: errorMessage | |
}; | |
expect(actual).toEqual(expected); | |
}); | |
}); | |
}); | |
describe('validation/required value', () => { | |
it('should handle valid plain value with no errors', () => { | |
const errorMessage = 'Required'; | |
const validations = [required('name', errorMessage), required('years', errorMessage)]; | |
const actual = applyValidators(validations)({ name: 'value', years: 100 }); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
it('should handle invalid plain value with errors', () => { | |
const errorMessage = 'Required'; | |
const validations = [ | |
required('name', errorMessage), | |
required('name2', errorMessage), | |
required('name3', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ name: '', name2: null }); | |
const expected = { | |
name: errorMessage, | |
name2: errorMessage, | |
name3: errorMessage | |
}; | |
expect(actual).toEqual(expected); | |
}); | |
it('should handle valid nested value with no errors', () => { | |
const errorMessage = 'Required'; | |
const validations = [required(['user', 'name', 'id'], errorMessage)]; | |
const actual = applyValidators(validations)({ user: { name: { id: 'value' } } }); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
it('should handle invalid nested value with nested errors object', () => { | |
const errorMessage = 'Required name'; | |
const errorMessageEmail = 'Required email'; | |
const validations = [ | |
required(['user', 'name', 'id'], errorMessage), | |
required(['user', 'email', 'value'], errorMessageEmail), | |
]; | |
const actual = applyValidators(validations)({ user: { name: { id: null }, email: { value: null} } }); | |
const expected = { user: { name: { id: errorMessage }, email: { value: errorMessageEmail } } }; | |
expect(actual).toEqual(expected); | |
}); | |
}); | |
test('feature/isCyrillic validator:valid value', () => { | |
const errorMessage = 'Needs to have only cyrillic symbols'; | |
const validations = [ | |
isCyrillic('name', errorMessage), | |
isCyrillic('name2', errorMessage), | |
isCyrillic('name3', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: 'Фамилия-Имя.отчество', | |
name2: '', | |
name3: null | |
}); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isCyrillic validator:invalid value', () => { | |
const errorMessage = 'Needs to have only cyrillic symbols'; | |
const validations = [ | |
isCyrillic('name', errorMessage), | |
isCyrillic('name2', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: 'ФамилияName', | |
name2: 'NameФамилия' | |
}); | |
const expected = { name: errorMessage, name2: errorMessage }; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isEmail validator:valid value', () => { | |
const errorMessage = 'Value is in the wrong format'; | |
const validations = [ | |
isEmail('name', errorMessage), | |
isEmail('name2', errorMessage), | |
isEmail('name3', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: 'mail@com.com', | |
name2: '', | |
name3: null | |
}); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isEmail validator:invalid value', () => { | |
const errorMessage = 'Value is in the wrong format'; | |
const validations = [ | |
isEmail('name', errorMessage), | |
isEmail('name2', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: 'mail.com.com', | |
name2: 'mail-com.com' | |
}); | |
const expected = { name: errorMessage, name2: errorMessage }; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isPassword validator:correct value', () => { | |
const errorMessage = 'Value is in the wrong format'; | |
const validations = [ | |
isPassword('name', errorMessage), | |
isPassword('name2', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: 'my1@longPassword', | |
name2: '1234564APassword!' | |
}); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isPassword validator:invalid value', () => { | |
const errorMessage = 'Value is in the wrong format'; | |
const validations = [ | |
isPassword('name', errorMessage), | |
isPassword('name2', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ name: 'short', name2: '123' }); | |
const expected = { name: errorMessage, name2: errorMessage }; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isEqual validator:correct value', () => { | |
const errorMessage = 'Values must be equal'; | |
const validations = [isEqual('name', 'name2')('name', errorMessage)]; | |
const actual = applyValidators(validations)({ | |
name: 'my1@longPassword', | |
name2: 'my1@longPassword' | |
}); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isEqual validator:invalid value', () => { | |
const errorMessage = 'Values must be equal'; | |
const validations = [ | |
isEqual('name', 'name2')('name', errorMessage), | |
isEqual('name3', 'name4')('name', errorMessage) | |
]; | |
const actual = applyValidators(validations)( | |
{ name: 'my1@longPassword', name2: 'my2@longPassword' }, | |
{ name3: 'my1@longPassword', name4: '' } | |
); | |
const expected = { name: errorMessage }; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isPhone validator:correct value', () => { | |
const errorMessage = 'Value must be in correct phone format'; | |
const validations = [ | |
isPhone('name', errorMessage), | |
isPhone('name2', errorMessage), | |
isPhone('name3', errorMessage), | |
isPhone('name4', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: '+7 (987) 654-32-10', | |
name2: '+7 (911) 111-11-11', | |
name3: null, | |
name4: {} | |
}); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isPhone validator:invalid value', () => { | |
const errorMessage = 'Value must be in correct phone format'; | |
const validations = [ | |
isPhone('name', errorMessage), | |
isPhone('name2', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: '+79876543210', | |
name2: '+7 (911) 1-11-11' | |
}); | |
const expected = { name: errorMessage, name2: errorMessage }; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isNotPhone validator:correct value', () => { | |
const errorMessage = 'Value must not be in phone format'; | |
const validations = [ | |
isNotPhone('name', errorMessage), | |
isNotPhone('name2', errorMessage), | |
isNotPhone('name3', errorMessage), | |
isNotPhone('name4', errorMessage) | |
]; | |
const actual = applyValidators(validations)({ | |
name: '+7 (987) 654-32-10', | |
name2: '123456789', | |
name3: null, | |
name4: {} | |
}); | |
const expected = {}; | |
expect(actual).toEqual(expected); | |
}); | |
test('feature/isNotPhone validator:invalid value', () => { | |
const errorMessage = 'Value must not be in phone format'; | |
const validations = [isNotPhone('name', errorMessage)]; | |
const actual = applyValidators(validations)({ name: '+79876543210' }); | |
const expected = { name: errorMessage }; | |
expect(actual).toEqual(expected); | |
}); | |
describe('List validator', () => { | |
test('should accept a list of not empty strings', () => { | |
const errorMessageAmount = 'Amount must not be an empty string'; | |
const errorMessageType = 'Type must not be an empty string'; | |
const validations = [ | |
isArrayValid('incomes', [ | |
required('amount', errorMessageAmount), | |
required('type', errorMessageType) | |
]) | |
]; | |
const actual = applyValidators(validations)({ | |
incomes: [ | |
{ amount: 500, type: 'salary' }, | |
{ amount: 100500, type: 'interest' } | |
] | |
}); | |
const expected = { incomes: [{}, {}] }; | |
expect(actual).toEqual(expected); | |
}); | |
test('should deny a list of partially empty strings', () => { | |
const errorMessageAmount = 'Amount must not be an empty string'; | |
const errorMessageType = 'Type must not be an empty string'; | |
const validations = [ | |
isArrayValid('incomes', [ | |
required('amount', errorMessageAmount), | |
required('type', errorMessageType) | |
]) | |
]; | |
const actual = applyValidators(validations)({ | |
incomes: [{ amount: 500, type: 'salary' }, { amount: 100500, type: '' }] | |
}); | |
const expected = { incomes: [{}, { type: errorMessageType }] }; | |
expect(actual).toEqual(expected); | |
}); | |
}); |
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 * as R from "ramda"; | |
const validate = (fn: (v: any) => any, key: unknown) => (values: {}) => { | |
return R.is(Array, key) | |
? R.pathSatisfies(fn, key as (string | number)[], values) | |
: R.propSatisfies(fn, key as string, values); | |
}; | |
const mergeErrors = (key: string, message: string) => | |
R.ifElse( | |
R.is(Array), | |
val => R.assocPath(val, message, {}), | |
val => R.objOf(val, message) | |
)(key); | |
/** | |
* Validator helper that determines if a path is fulfilling given predicate function, | |
* therefore validating value under given key | |
* @param key | |
* @param message | |
* @param fn | |
* @returns {*} | |
*/ | |
export const validator = R.curry((fn, key, message) => | |
R.ifElse(validate(fn, key), R.always({}), R.always(mergeErrors(key, message))) | |
); | |
export const shallowValidator = R.curry((fn, key, message) => | |
R.ifElse(fn, R.always({}), R.always({ [key]: message })) | |
); | |
const isNull = R.anyPass([R.isEmpty, R.isNil]); | |
const isNotEmpty = R.complement(isNull); | |
const isValidOrEmpty = (testFn: any) => | |
validator(R.ifElse(isNull, R.always(true), testFn)); | |
export const branchValidator = ( | |
key: string, | |
value: any, | |
onTrue: any, | |
onFalse = R.always(true) | |
// @ts-ignore | |
) => R.ifElse(R.propEq(key, value), onTrue, onFalse); | |
export const isArrayValid = (key: string, validations: any) => { | |
return R.compose( | |
R.ifElse( | |
R.is(Array), | |
R.compose( | |
R.objOf(key), | |
R.map(applyValidators(validations)) | |
), | |
R.always({}) | |
), | |
// @ts-ignore | |
R.path(key) | |
); | |
}; | |
/** | |
* Required validator that checks if value from the given key is not null | |
* @param key {String} access key | |
* @param message {String} error message | |
* @returns {Function} accepts values object | |
*/ | |
export const required = validator(isNotEmpty); | |
export const isEqual = (aKey: string, bKey: string) => | |
// @ts-ignore | |
shallowValidator(R.converge(R.equals, [R.path(aKey), R.path(bKey)])); | |
const phoneReg = /^\+7 \([0-9]{3}\) [0-9]{3}-[0-9]{2}-[0-9]{2}$/; | |
export const isPhone = isValidOrEmpty(R.test(phoneReg)); | |
const notPhoneReg = /^(\+7)+[0-9]{10}$/; | |
export const isNotPhone = isValidOrEmpty( | |
R.compose( | |
R.not, | |
R.test(notPhoneReg) | |
) | |
); | |
const cyrillicReg = /^[а-яА-Я]+((?![a-zA-Z0-9]).)*$/; | |
export const isCyrillic = isValidOrEmpty(R.test(cyrillicReg)); | |
export const isEmail = isValidOrEmpty(R.contains("@")); | |
const passwordReg = /^[-0-9a-zA-Z_!"#$%&‘()*+,./:;<=>?@\][\\^{|}~]{8,50}$/gi; | |
export const isPassword = validator(R.test(passwordReg)); | |
const loginReg = /^[A-Za-z\d\-@_]{6,50}$/gi; | |
export const isLogin = validator(R.test(loginReg)); | |
/** | |
* Returns a function that applies given validators list to an object with key values | |
* @param validators {Array<Function>} validators list containing functions with key to validate | |
* and error message to pop when validation fails | |
* @returns {Function} | |
*/ | |
const mergeDeepAll = (data: any) => R.reduce(R.mergeDeepLeft, {}, data); | |
// @ts-ignore | |
export const applyValidators = R.converge(R.unapply(mergeDeepAll)); | |
// Control quotients list for INN validation | |
const q1 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]; | |
const q2 = R.drop(1, q1); | |
const q3 = R.drop(2, q1); | |
/** | |
* Calculates sum of every element of a first array, | |
* multiplied by corresponding element of second array | |
*/ | |
const sumAndProduct = R.curry( | |
R.compose( | |
R.sum, | |
R.map(R.product), | |
// @ts-ignore | |
R.zip | |
) | |
); | |
/** | |
* Checks if a value is greater than 9 then return 0, returns original value otherwise | |
*/ | |
const checkMagnitude = R.when(R.gt(R.__, 9), R.always(0)); | |
const DIVIDER = 11; | |
/** | |
* validates given inn sequence, by calculating checksum based on given quotients array | |
* @param index {Number} INN index at which control value will be compared to | |
* @param q {Array} quotient array for the step | |
* @returns {function(*=): *} | |
*/ | |
const compareChecksum = (index: number, q: number[]) => (inn: any) => | |
R.compose( | |
R.equals(+inn[index]), | |
checkMagnitude, | |
R.flip(R.modulo)(DIVIDER), | |
// @ts-ignore | |
sumAndProduct(q), | |
R.take(index) | |
)(inn); | |
// control step for IE INN length | |
const ieStep0 = R.propEq("length", 12); | |
// control step for the last but one IE INN digit | |
const ieStep1 = compareChecksum(10, q2); | |
// control step for the last IE INN digit | |
const ieStep2 = compareChecksum(11, q1); | |
// control step for Entity INN length | |
const entityStep0 = R.propEq("length", 10); | |
// control step for the last Entity INN digit | |
const entityStep1 = compareChecksum(9, q3); | |
export const isIEINNValid = isValidOrEmpty( | |
R.allPass([ieStep0, ieStep1, ieStep2]) | |
); | |
export const isEntityINNValid = isValidOrEmpty( | |
R.allPass([entityStep0, entityStep1]) | |
); | |
// @ts-ignore | |
const ogrnStep0 = R.propEq("length"); | |
/** | |
* последняя цифра остатка от деления значения огрн без последнего знака, | |
* должна быть равна последнему знаку | |
* @param divider | |
* @returns {function(*=): *} | |
*/ | |
const ogrnStep1 = (divider: number) => (value: any) => | |
R.compose( | |
R.equals(R.last(value)), | |
R.last, | |
// @ts-ignore | |
toString, | |
R.flip(R.modulo)(divider), | |
R.init | |
)(value); | |
const isOgrnValid = (length: number, divider: number) => | |
R.allPass([R.is(String), ogrnStep0(length), ogrnStep1(divider)]); | |
export const isEntityOgrnValid = isValidOrEmpty(isOgrnValid(13, 11)); | |
export const isIeOgrnValid = isValidOrEmpty(isOgrnValid(15, 13)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment