Improve validation messages and add more validation rules
This commit is contained in:
parent
8882df4c0d
commit
b13d70bfc0
@ -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}.`;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user