Validator

You can add validations to any class or method using the validate decorator. The method will be called if the validation is valid.
Validations are using appolo-validator.

Installation#

npm i @appolo/validator

Options#

keyDescriptionTypeDefault
convertwhen true, attempts to cast values to the required types (e.g. a string to a number)booleantrue
stripUnknownremove unknown elements from objects and arraysbooleantrue

in config/modules/all.ts

import {App} from 'appolo';
import {ValidationModule} from '@appolo/validator';
export = async function (app:App) {
app.module.use(ValidationModule.for({
allowUnknown:true
}));
}

Usage#

Validate Object#

validate with an object will run on the first argument

import {controller,Controller,IRequest,IResponse,get,query} from '@appolo/route';
import {inject} from '@appolo/inject';
import {string,validate,number} from '@appolo/validation';
@controller()
export class TestController extends Controller{
@inject() dataManager:DataManager
@get("/search/")
@validate({
search:string().required(),
pageSize:number().default(20),
page:number().default(1)
})
public async search (@query() query) {
let {search,page,pageSize} = query;
return await this.dataManager.search(search,page,pageSize)
}
}

Custom Options#

custom validate with options

keyDescriptionTypeDefault
convertwhen true, attempts to cast values to the required types (e.g. a string to a number)booleantrue
stripUnknownremove unknown elements from objects and arraysbooleantrue
groupsgroups of the validationstring[][]
import {controller,inject,Controller,IRequest,IResponse,get} from 'appolo';
import {validate} from '@appolo/validation';
@controller()
export class TestController extends Controller{
@inject() dataManager:DataManager
@get("/search/")
@validate({
search:string().required(),
pageSize:number().default(20),
page:number().default(1)
},{stripUnknown:false,groups:["search"]})
public async search (@query() query) {
let {search,page,pageSize} = query;
return await this.dataManager.getSearchResults(search,page,pageSize)
}
}

Validation Fail#

If the params are not valid, BadRequestError will return a 400 Bad Request response with detailed validation errors.

{
status: 400,
message: "Bad Request",
error: "userId is required"
}

Validation Model#

you can use the validations as method decorators

import {string,number} from '@appolo/validation';
export class SearchModel {
@string().required()
search: string;
@number().required()
pageSize: number
@number().default(1)
page: number
}

then in the controller

import {controller,Controller,IRequest,IResponse,validator,get,query} from '@appolo/route';
import {inject} from '@appolo/inject';
import {validate} from '@appolo/validation';
@controller()
export class TestController extends Controller{
@inject() dataManager:DataManager
@get("/search/")
public async search (@validate() @query() query:SearchModel) {
let {search,page,pageSize} = query;
return await this.dataManager.getSearchResults(search,page,pageSize)
}
}

Custom options#

import {object,number,string} from '@appolo/validator';
@schema(object().options({stripUnknown:false}))
export class SearchModel {
@string().required()
search: string;
@number().required()
pageSize: number
@number().default(1)
page: number
}

Inherit Model#

import {string} from '@appolo/validator';
export class BaseSearchModel {
@string().required()
search: string;
}
export class SearchModel extends BaseSearchModel{
@number().required()
pageSize: number
@number().default(1)
page: number
}

Nested Model#

nested model can be an object or array

import {string,number,param,schema} from '@appolo/validator';
export class SearchModel {
@string().required()
search: string;
@number().required()
pageSize: number
@number().default(1)
page: number
}
export class PageModel{
@object().keys(SearchModel)
search:SearchModel
@array().items(SearchModel)
searches:SearchModel[]
@array().items(SearchModel).required()
searches2:SearchModel[]
}

Custom message#

import {object,number,string} from '@appolo/validator';
export class SearchModel {
@string({message:"invalid string"}).max(5,{message:"invalid string ${arg0} len"}).required()
search: string;
}

Groups#

when groups defined on a schema or constrain it will only run when validation called with matched group

import {object,number,string} from '@appolo/validator';
export class SearchModel {
@string().max(5,{groups:["test"]}).required().groups(["test2"])
search: string;
}
@define()
export class TestController{
public async search (@validate({groups:["test"]}) query:SearchModel) {
...
}
}

Custom Validator#

it is possible to define custom validators as standalone or with inject.

custom validator must implement IConstraint then register the validator using registerConstraint

@define()
@singleton()
export class RangeValidator implements IConstraint {
@inject() env:IEnv
public async validate(params: ValidationParams): Promise<IConstraintValidateResult> {
let isValid = params.value >= params.args[0] && params.value < params.args[1];
return {isValid};
}
public get type(): string {
return "range"
}
public get defaultMessage(): string {
return "${property} has invalid range "
}
}

register the validator to the base schema

registerConstraint.extend({
base: NumberSchema,
name: "range",
constraint: RangeValidator,
inject: true // set true to use injector to create the validator instance
});

add the validator types to base schema

declare module "@appolo/validator" {
export interface NumberSchema {
range(min: number, max: number): this;
}
}

now we can use the validator

@validate(number().range(1, 3))
public async someMethod(num: number): Promise<any> {
return num
}

string constrain will run but not max constrain