Skip to content

Instantly share code, notes, and snippets.

@xamedow
Created August 21, 2019 05:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xamedow/f8638919096a9db74e038186cdcfcc95 to your computer and use it in GitHub Desktop.
Save xamedow/f8638919096a9db74e038186cdcfcc95 to your computer and use it in GitHub Desktop.
final form validators
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);
});
});
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