UNPKG

blow-validate

Version:
135 lines (118 loc) 4.08 kB
'use strict'; 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); } }