UNPKG

@xroom.app/validators

Version:
245 lines (198 loc) 8.45 kB
const E = require('@xroom.app/data-types/lib/either') const ER = require('./errors') const T = require('./basic') // SECTION Constants /** @type {T.Validator<number>} */ const stringToLength = data => typeof data === 'string' ? E.right(data.length) : E.left([ER.typeError('string', data)]) // SECTION Tests describe('string validator', () => { it('should return data if string passed', () => { expect(T.string('')).toMatchObject(E.right('')) }) it('should return TypeError if not string passed', () => { expect(T.string(1)).toMatchObject(E.left([ER.typeError('string', 1)])) }) }) describe('number validator', () => { it('should return data if number passed', () => { expect(T.number(0)).toMatchObject(E.right(0)) }) it('should return TypeError if not number passed', () => { expect(T.number('')).toMatchObject(E.left([ER.typeError('number', '')])) }) }) describe('boolean validator', () => { it('should return data if boolean passed', () => { expect(T.boolean(true)).toMatchObject(E.right(true)) }) it('should return TypeError if not boolean passed', () => { expect(T.boolean(1)).toMatchObject(E.left([ER.typeError('boolean', 1)])) }) }) describe('nullVal validator', () => { it('should return data if null passed', () => { expect(T.nullVal(null)).toMatchObject(E.right(null)) }) it('should return TypeError if not null passed', () => { expect(T.nullVal(1)).toMatchObject(E.left([ER.typeError('null', 1)])) }) }) describe('undef validator', () => { it('should return data if undefined passed', () => { expect(T.undef(undefined)).toMatchObject(E.right(undefined)) }) it('should return TypeError if not null passed', () => { expect(T.undef(1)).toMatchObject(E.left([ER.typeError('undefined', 1)])) }) }) describe('literal validator', () => { it('should return data if the same literal passed', () => { expect(T.literal('123')('123')).toMatchObject(E.right('123')) }) it('should return ConditionError if not same literal passed', () => { expect(T.literal('123')('')).toMatchObject(E.left([ER.conditionError('equals \'123\'', '')])) expect(T.literal(123)('')).toMatchObject(E.left([ER.conditionError('equals 123', '')])) expect(T.literal(true)('')).toMatchObject(E.left([ER.conditionError('equals true', '')])) }) }) describe('array validator', () => { it('should return data if valid array passed', () => { expect(T.array(T.number)([1, 2, 3])).toMatchObject(E.right([1, 2, 3])) }) it('should return type error for non array type', () => { expect(T.array(T.number)(1)).toMatchObject(E.left([ER.typeError('Array', 1)])) }) it('should return array container error if elements not match type', () => { expect(T.array(T.number)([1, 2, true])).toMatchObject(E.left([ ER.containerError('Array', 1), ER.typeError('number', true) ])) }) it('should return new array with elements mapped by validator passed', () => { expect(T.array(stringToLength)(['0', '01', '012'])).toMatchObject(E.right([1, 2, 3])) }) it('should return error if element doesn\'t match mapping validator passed', () => { expect(T.array(stringToLength)(['0', '01', 3])).toMatchObject(E.left([ ER.containerError('Array', 1), ER.typeError('string', 3) ])) }) }) describe('tuple validator', () => { it('should return data if valid tuple passed', () => { expect(T.tuple([T.number, T.string])([0, ''])).toMatchObject(E.right([0, ''])) }) it('should return type error for non tuple type', () => { expect(T.tuple([T.number])(1)).toMatchObject(E.left([ER.typeError('Tuple', 1)])) }) it('should return tuple container error if elements not match types', () => { expect(T.tuple([T.number, T.string])([1, 2])).toMatchObject(E.left([ ER.containerError('Tuple', 1), ER.fieldError('1'), ER.typeError('string', 2) ])) expect(T.tuple([T.number, T.string])(['', 2])).toMatchObject(E.left([ ER.containerError('Tuple', 1), ER.fieldError('0'), ER.typeError('number', '') ])) expect(T.tuple([T.number, T.string])(['', ''])).toMatchObject(E.left([ ER.containerError('Tuple', 1), ER.fieldError('0'), ER.typeError('number', '2'), ])) }) it('should return literal error if length doesn\'t match', () => { expect(T.tuple([T.number, T.string])([1, 2, 3])).toMatchObject(E.left([ ER.containerError('Tuple', 1), ER.fieldError('length'), ER.conditionError('equals 2', 3), ])) }) it('should return new tuple with elements mapped by validators passed', () => { expect(T.tuple([stringToLength, stringToLength, stringToLength])(['0', '01', '012'])).toMatchObject(E.right([1, 2, 3])) }) }) describe('enumeration validator', () => { const boolEnum = { false: false, true: true } it('should return data if enumeration member passed', () => { expect(T.enumeration(boolEnum)(true)).toMatchObject(E.right(true)) }) it('should return condition error if non enum member passed', () => { expect(T.enumeration(boolEnum)(0)).toMatchObject(E.left([ER.conditionError('exists in [false,true]', 0)])) }) }) describe('union validator', () => { it('should return data if any of validators passed', () => { expect(T.union([T.number, T.string])(0)).toMatchObject(E.right(0)) expect(T.union([T.number, T.string])('')).toMatchObject(E.right('')) }) it('should return container error if no validators passed', () => { expect(T.union([T.number, T.string])(false)).toMatchObject(E.left([ ER.containerError('Union', 2), ER.typeError('number', false), ER.typeError('string', false), ])) }) it('should act as internal validator if tuple with 1 element passed', () => { expect(T.union([T.number])('')).toMatchObject(E.left([ER.typeError('number', '')])) }) }) describe('prop validator', () => { it('should return type error if data is not object', () => { expect(T.prop('required', 'foo', T.number)(0)).toMatchObject(E.left([ER.typeError('Object', 0)])) expect(T.prop('optional', 'foo', T.number)(0)).toMatchObject(E.left([ER.typeError('Object', 0)])) }) it('should return empty object if object doesn\'t contain prop but validator is optional', () => { expect(T.prop('optional', 'foo', T.number)({ bar: 0 })).toMatchObject(E.right({})) }) it('should return error if object doesn\'t contain required prop', () => { expect(T.prop('required', 'foo', T.number)({ bar: 0 })).toMatchObject(E.left([ ER.fieldError('foo'), ER.notFound ])) }) it('should return error if prop exists, but not matches validator', () => { expect(T.prop('required', 'foo', T.number)({ foo: '' })).toMatchObject(E.left([ ER.containerError('Object', 1), ER.fieldError('foo'), ER.typeError('number', '') ])) expect(T.prop('optional', 'foo', T.number)({ foo: '' })).toMatchObject(E.left([ ER.containerError('Object', 1), ER.fieldError('foo'), ER.typeError('number', '') ])) }) it('should return data if prop exists and matches validator', () => { expect(T.prop('required', 'foo', T.number)({ foo: 0 })).toMatchObject(E.right({ foo: 0 })) expect(T.prop('optional', 'foo', T.number)({ foo: 0 })).toMatchObject(E.right({ foo: 0 })) }) }) describe('type validator', () => { const foo = T.prop('required', 'a', T.number) const bar = T.prop('optional', 'b', T.string) const validator = T.type([foo, bar]) it('should return data if each prop validator passed', () => { expect(validator({ a: 0, b: '' })).toMatchObject(E.right({ a: 0, b: '' })) expect(validator({ a: 0 })).toMatchObject(E.right({ a: 0 })) }) it('should return TypeError if non object passed', () => { expect(validator(0)).toMatchObject(E.left([ER.typeError('Object', 0)])) expect(validator(null)).toMatchObject(E.left([ER.typeError('Object', null)])) expect(validator([0])).toMatchObject(E.left([ER.typeError('Object', [0])])) }) it('should return NotFoundError if one of fields doesn\'t exist', () => { expect(validator({ b: '' })).toMatchObject(E.left([ ER.fieldError('a'), ER.notFound ])) }) it('should return TypeError if field type doesn\'t match', () => { expect(validator({ a: 0, b: 1 })).toMatchObject(E.left([ ER.containerError('Object', 1), ER.fieldError('b'), ER.typeError('string', 1) ])) }) })