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 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}.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user