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