Add server/src/validator
This commit is contained in:
parent
715c40f60f
commit
6e94b68598
4
packages/server/src/validator/IValidationMessage.ts
Normal file
4
packages/server/src/validator/IValidationMessage.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface IValidationMessage {
|
||||||
|
readonly property: string | number | symbol
|
||||||
|
readonly message: string
|
||||||
|
}
|
||||||
17
packages/server/src/validator/ValidationError.ts
Normal file
17
packages/server/src/validator/ValidationError.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
84
packages/server/src/validator/Validator.test.ts
Normal file
84
packages/server/src/validator/Validator.test.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
48
packages/server/src/validator/Validator.ts
Normal file
48
packages/server/src/validator/Validator.ts
Normal 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})
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/server/src/validator/index.ts
Normal file
3
packages/server/src/validator/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './IValidationMessage'
|
||||||
|
export * from './ValidationError'
|
||||||
|
export * from './Validator'
|
||||||
Loading…
x
Reference in New Issue
Block a user