Improve validation messages and add more validation rules

This commit is contained in:
Alice Gaudon 2020-04-25 16:08:53 +02:00
parent 8882df4c0d
commit b13d70bfc0

View File

@ -1,6 +1,7 @@
import Model from "./Model";
import Query from "./Query";
import {Connection} from "mysql";
import {Type} from "../Utils";
export default class Validator<T> {
private readonly steps: ValidationStep<T>[] = [];
@ -48,11 +49,11 @@ export default class Validator<T> {
return this;
}
public acceptUndefined(): Validator<T> {
public acceptUndefined(alsoAcceptEmptyString: boolean = false): Validator<T> {
this.addStep({
verifyStep: () => true,
throw: null,
interrupt: val => val === undefined || val === null,
interrupt: val => val === undefined || val === null || (alsoAcceptEmptyString && typeof val === 'string' && val.length === 0),
isFormat: true,
});
return this;
@ -67,6 +68,15 @@ export default class Validator<T> {
return this;
}
public sameAs(otherName?: string, other?: T): Validator<T> {
this.addStep({
verifyStep: val => val === other,
throw: () => new DifferentThanError(otherName),
isFormat: true,
});
return this;
}
public regexp(regexp: RegExp): Validator<T> {
this.validationAttributes.push(`pattern="${regexp}"`);
this.addStep({
@ -92,7 +102,7 @@ export default class Validator<T> {
public minLength(minLength: number): Validator<T> {
this.addStep({
verifyStep: val => (<any>val).length >= minLength,
throw: () => new BadLengthValidationError(minLength, 1000000),
throw: () => new TooShortError(minLength),
isFormat: true,
});
return this;
@ -104,7 +114,7 @@ export default class Validator<T> {
public maxLength(maxLength: number): Validator<T> {
this.addStep({
verifyStep: val => (<any>val).length <= maxLength,
throw: () => new BadLengthValidationError(0, maxLength),
throw: () => new TooLongError(maxLength),
isFormat: true,
});
return this;
@ -158,19 +168,20 @@ export default class Validator<T> {
return this;
}
public unique(model: Model, querySupplier?: () => Query): Validator<T> {
public unique<M extends Model>(model: M | Type<M>, foreignKey?: string, querySupplier?: () => Query): Validator<T> {
this.addStep({
verifyStep: async (val, thingName, c) => {
if (!foreignKey) foreignKey = thingName;
let query: Query;
if (querySupplier) {
query = querySupplier().where(thingName, val);
query = querySupplier().where(foreignKey, val);
} else {
query = (<any>model.constructor).select('1').where(thingName, val);
query = (model instanceof Model ? <any>model.constructor : model).select('1').where(foreignKey, val);
}
if (typeof model.id === 'number') query = query.whereNot('id', model.id);
if (model instanceof Model && typeof model.id === 'number') query = query.whereNot('id', model.id);
return (await query.execute(c)).results.length === 0;
},
throw: () => new AlreadyExistsValidationError(model.table),
throw: () => new AlreadyExistsValidationError((<any>model).table),
isFormat: false,
});
return this;
@ -210,26 +221,37 @@ interface ValidationStep<T> {
}
export class ValidationBag extends Error {
private readonly messages: { [p: string]: any } = {};
private readonly errors: ValidationError[] = [];
public addMessage(err: ValidationError) {
if (!err.thingName) {
throw new Error('Null thing name');
}
if (!err.thingName) throw new Error('Null thing name');
this.errors.push(err);
}
this.messages[err.thingName] = {
name: err.name,
message: err.message,
value: err.value,
};
public addBag(otherBag: ValidationBag) {
for (const error of otherBag.errors) {
this.errors.push(error);
}
}
public hasMessages(): boolean {
return Object.keys(this.messages).length > 0;
return this.errors.length > 0;
}
public getMessages(): { [p: string]: ValidationError } {
return this.messages;
const messages: { [p: string]: ValidationError } = {};
for (const err of this.errors) {
messages[err.thingName!] = {
name: err.name,
message: err.message,
value: err.value,
};
}
return messages;
}
public getErrors(): ValidationError[] {
return this.errors;
}
}
@ -258,6 +280,32 @@ export class BadLengthValidationError extends ValidationError {
}
}
export class TooShortError extends ValidationError {
private readonly minLength: number;
constructor(minLength: number) {
super();
this.minLength = minLength;
}
public get message(): string {
return `${this.thingName} must be at least ${this.minLength} characters.`;
}
}
export class TooLongError extends ValidationError {
private readonly maxLength: number;
constructor(maxLength: number) {
super();
this.maxLength = maxLength;
}
public get message(): string {
return `${this.thingName} must be at most ${this.maxLength} characters.`;
}
}
export class BadValueValidationError extends ValidationError {
private readonly expectedValue: any;
@ -271,6 +319,19 @@ export class BadValueValidationError extends ValidationError {
}
}
export class DifferentThanError extends ValidationError {
private readonly otherName: any;
constructor(otherName: any) {
super();
this.otherName = otherName;
}
public get message(): string {
return `This should be the same as ${this.otherName}.`
}
}
export class OutOfRangeValidationError extends ValidationError {
private readonly min?: number;
private readonly max?: number;
@ -312,7 +373,7 @@ export class AlreadyExistsValidationError extends ValidationError {
}
public get message(): string {
return `${this.value} already exists in ${this.table}.${this.thingName}.`;
return `${this.thingName} already exists in ${this.table}.`;
}
}