blow-validate
Version:
Validation for Blow entities.
135 lines (118 loc) • 4.08 kB
text/typescript
;
import * as Joi from 'joi';
import {Observable} from 'rxjs';
import {ValidationResult} from './ValidationResult';
import {ValidationError} from './ValidationError';
export interface ValidationSchema {
[property: string]: {
[validator: string]: any
};
}
const JOIValidations = [
{ validation: 'valid' },
{ validation: 'invalid' },
{ validation: 'required' },
{ validation: 'optional' },
{ validation: 'forbidden' },
{ validation: 'min' },
{ validation: 'max' },
{ validation: 'length' },
{ validation: 'unique' },
{ validation: 'pattern' },
{ validation: 'email' },
{ validation: 'string' },
{ validation: 'number' },
{ validation: 'date' },
{ validation: 'array' },
{ validation: 'object' }
];
const JOI_VALIDATION_OPTIONS = {
abortEarly: false,
allowUnknown: true
};
function isJOIValidation(name) {
return JOIValidations.some(v => v['validation'] === name);
}
export class Validator {
protected _schema: ValidationSchema;
protected _compiledJoiSchema;
protected _compiledCustomSchema;
constructor(schema?: ValidationSchema) {
this._schema = schema || {};
this._compile();
}
protected _compile() {
const properties = Object.keys(this._schema);
this._compiledJoiSchema = {};
this._compiledCustomSchema = {};
for (const property of properties) {
const validations = Object.keys(this._schema[property]);
let propertyJoiSchema;
let propertyCustomSchema;
for (const validation of validations) {
if (isJOIValidation(validation)) {
if (!propertyJoiSchema) {
propertyJoiSchema = Joi;
}
let options = this._schema[property][validation];
if (validation === 'email') {
options = undefined;
}
propertyJoiSchema = propertyJoiSchema[validation](options);
} else {
if (!propertyCustomSchema) {
propertyCustomSchema = [];
}
propertyCustomSchema.push({ name: validation, fn: this._schema[property][validation] });
}
}
if (propertyJoiSchema) {
this._compiledJoiSchema[property] = propertyJoiSchema;
}
if (propertyCustomSchema) {
this._compiledCustomSchema[property] = propertyCustomSchema;
}
}
}
protected _validateJoi(data: { [property: string]: any }): Observable<ValidationResult> {
if (!Object.keys(this._compiledJoiSchema).length) {
return Observable.of(new ValidationResult());
}
return Observable.create(subscriber => {
Joi.validate(data, this._compiledJoiSchema, JOI_VALIDATION_OPTIONS, err => {
if (err) {
const createError = e => ValidationError.create({property: e.path, type: e.type.split('.')[1], message: e.message});
subscriber.next(<any>err.details.map(createError));
}
subscriber.complete();
});
})
.defaultIfEmpty()
.map(errors => new ValidationResult(errors));
}
protected _validateCustom(data: { [property: string]: any }): Observable<ValidationResult> {
const properties = Object.keys(this._compiledCustomSchema);
if (!properties.length) {
return Observable.of(ValidationResult.create());
}
return Observable.from<string>(properties)
.map(property => [property, this._compiledCustomSchema[property]])
.mergeMap(row => Observable.from(row[1]).mergeMap((v: { fn: any }) => v.fn.bind(data)(row[0])))
.filter(e => !!e)
.map(e => ValidationError.create({property: (<any>e).path, type: (<any>e).type, message: (<any>e).message}))
.toArray()
.defaultIfEmpty()
.map(ValidationResult.create);
}
validate(data: { [property: string]: any }): Observable<ValidationResult> {
const mapResults = (joiResult, customResult) => joiResult.merge(customResult);
return Observable.combineLatest(
this._validateJoi(data),
this._validateCustom(data),
mapResults
);
}
static create(schema?: ValidationSchema): Validator {
return new Validator(schema);
}
}