Add server/src/validator

This commit is contained in:
Jerko Steiner 2019-01-22 11:57:05 +01:00
parent 715c40f60f
commit 6e94b68598
5 changed files with 156 additions and 0 deletions

View File

@ -0,0 +1,4 @@
export interface IValidationMessage {
readonly property: string | number | symbol
readonly message: string
}

View File

@ -0,0 +1,17 @@
import {IValidationMessage} from './IValidationMessage'
export class ValidationError extends Error {
readonly name: string
constructor(
readonly errors: IValidationMessage[],
message?: string,
) {
super(
message
? message
: 'Validation failed on properties: ' +
errors.map(e => e.property).join(', '))
this.name = 'ValidationError'
Error.captureStackTrace(this)
}
}

View File

@ -0,0 +1,84 @@
import {Validator} from './Validator'
describe('Validator', () => {
const entity = {
a: 0,
b: 1,
c: 2,
d: undefined,
e: false,
f: null,
}
let v!: Validator<typeof entity>
beforeEach(() => {
v = new Validator(entity)
})
describe('constructor', () => {
it('throws when an entity is not provided', () => {
expect(() => new Validator(undefined))
.toThrowError(/record could not be found/)
})
})
describe('ensure', () => {
it('adds an error when a value is falsy', () => {
v.ensure('a')
expect(v.errors.length).toBe(1)
v.ensure('e')
expect(v.errors.length).toBe(2)
})
it('adds an error when a value does not exist', () => {
v.ensure('b')
expect(v.errors.length).toBe(0)
v.ensure('b')
expect(v.errors.length).toBe(0)
v.ensure('c')
expect(v.errors.length).toBe(0)
v.ensure('d')
expect(v.errors.length).toBe(1)
})
it('adds an error when a value does not match', () => {
v.ensure('a', entity.a)
v.ensure('b', entity.b)
v.ensure('c', entity.c)
v.ensure('d', entity.d)
v.ensure('e', entity.e)
v.ensure('f', entity.f)
expect(v.errors.length).toBe(0)
v.ensure('f', undefined)
expect(v.errors.length).toBe(1)
})
})
describe('getError', () => {
it('returns undefined when no error', () => {
v.ensure('a', entity.a)
expect(v.getError()).toBe(undefined)
})
it('returns an error when a validation errors was encountered', () => {
v.ensure('a', 999)
const err = v.getError()!
expect(err).toBeTruthy()
expect(err.name).toEqual('ValidationError')
expect(err.message).toMatch(/Validation failed on properties/)
})
})
describe('throw', () => {
it('does nothing when no validation errors', () => {
v.ensure('a', entity.a)
v.throw()
})
it('throws when there are validation errors', () => {
v.ensure('a', 999)
expect(() => v.throw()).toThrowError()
})
})
})

View File

@ -0,0 +1,48 @@
import {IValidationMessage} from './IValidationMessage'
import {ValidationError} from './ValidationError'
export class Validator<T> {
readonly errors: IValidationMessage[] = []
readonly entity: T
constructor(entity: T | undefined) {
if (!entity) {
throw new ValidationError([], 'The record could not be found')
}
this.entity = entity
}
ensure(property: keyof T, value?: any): this {
if (arguments.length === 1) {
if (!this.entity[property]) {
this.addError(property, `The property "${property}" is invalid`)
}
return this
}
if (this.entity[property] !== value) {
this.addError(property,
`The property "${property}" should be equal to "${value}"` +
` but the actual value is "${this.entity[property]}"`)
}
return this
}
getError(): ValidationError | undefined {
if (this.errors.length) {
return new ValidationError(this.errors)
}
}
throw() {
const error = this.getError()
if (error) {
throw error
}
}
protected addError(property: keyof T, message: string) {
this.errors.push({property, message})
}
}

View File

@ -0,0 +1,3 @@
export * from './IValidationMessage'
export * from './ValidationError'
export * from './Validator'