From 6e94b68598c7e0c8efe42d9243222a99a535ff0c Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Tue, 22 Jan 2019 11:57:05 +0100 Subject: [PATCH] Add server/src/validator --- .../src/validator/IValidationMessage.ts | 4 + .../server/src/validator/ValidationError.ts | 17 ++++ .../server/src/validator/Validator.test.ts | 84 +++++++++++++++++++ packages/server/src/validator/Validator.ts | 48 +++++++++++ packages/server/src/validator/index.ts | 3 + 5 files changed, 156 insertions(+) create mode 100644 packages/server/src/validator/IValidationMessage.ts create mode 100644 packages/server/src/validator/ValidationError.ts create mode 100644 packages/server/src/validator/Validator.test.ts create mode 100644 packages/server/src/validator/Validator.ts create mode 100644 packages/server/src/validator/index.ts diff --git a/packages/server/src/validator/IValidationMessage.ts b/packages/server/src/validator/IValidationMessage.ts new file mode 100644 index 0000000..6beb9e4 --- /dev/null +++ b/packages/server/src/validator/IValidationMessage.ts @@ -0,0 +1,4 @@ +export interface IValidationMessage { + readonly property: string | number | symbol + readonly message: string +} diff --git a/packages/server/src/validator/ValidationError.ts b/packages/server/src/validator/ValidationError.ts new file mode 100644 index 0000000..27aab28 --- /dev/null +++ b/packages/server/src/validator/ValidationError.ts @@ -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) + } +} diff --git a/packages/server/src/validator/Validator.test.ts b/packages/server/src/validator/Validator.test.ts new file mode 100644 index 0000000..72e0538 --- /dev/null +++ b/packages/server/src/validator/Validator.test.ts @@ -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 + 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() + }) + }) + +}) diff --git a/packages/server/src/validator/Validator.ts b/packages/server/src/validator/Validator.ts new file mode 100644 index 0000000..afb106d --- /dev/null +++ b/packages/server/src/validator/Validator.ts @@ -0,0 +1,48 @@ +import {IValidationMessage} from './IValidationMessage' +import {ValidationError} from './ValidationError' + +export class Validator { + + 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}) + } +} diff --git a/packages/server/src/validator/index.ts b/packages/server/src/validator/index.ts new file mode 100644 index 0000000..31baf0f --- /dev/null +++ b/packages/server/src/validator/index.ts @@ -0,0 +1,3 @@ +export * from './IValidationMessage' +export * from './ValidationError' +export * from './Validator'