Writing a validator is easy!
Base of a validator function is a traverse through object's properties.
Then you need to check your value with some basic if/else
to validate input data.
The final validation schema will look like this:
const Schema = {
'Username': 'Required|Alphabet|Min:3|Max:10',
'Password': 'Required|Alphabet|Min:3|Max:10',
'Profile': {
'Age': 'Required|Numeric|Gt:17|Lt:99',
'Sex': 'Required|In:Male,Female',
}
}
Less talk more code, let's start with traverse function:
enum SchemaType {
string,
object,
array
}
.
.
.
class Validator {
private MainValidator(Schema:any, Data:any,Path?:string) {
for (const key in Schema) {
const Rule = Schema[key];
const Node = (Path ? Path + "." : "") + key;
const RuleData = Data ? Data[key] : {};
const InputSchemaType:SchemaType | undefined = this.GetSchemaType(Rule);
switch (InputSchemaType) {
case SchemaType.object:
this.MainValidator(Rule, RuleData,key);
break;
case SchemaType.array:
this.ArrayValidator(Rule[0],RuleData,key);
break;
default:
this.InnerValidator(Rule, RuleData,Node);
break;
}
}
}
.
.
.
private GetSchemaType(Rule:any):SchemaType | undefined {
if (typeof Rule === "string") {
return SchemaType.string;
}
else if (typeof Rule === "object" && !Array.isArray(Rule))
{
return SchemaType.object;
}
else if (typeof Rule === "object" && Array.isArray(Rule))
{
return SchemaType.array;
}
else {
return;
}
}
}
MainValidator
method is the base of our validation then we need to define the branches of the tree, ArrayValidator
and InnerValidator
.
.
.
.
private ArrayValidator(Rule:any, Data:any, key?:string) {
let index = 0;
do {
this.MainValidator(Rule, Data[index] , key+"."+index);
index++
} while ((Data || []).length > index);
}
.
.
.
private InnerValidator(rules:any,value:any,Node:string) {
const FnNameAndArgs = this.GetFnNameAndArgs(rules);
for (let index = 0; index < FnNameAndArgs.length; index++) {
const FnNameAndArg = FnNameAndArgs[index];
const Result = this.Validators.get(FnNameAndArg.name)?.call(this,value,...FnNameAndArg.args)
if(Result && !Result.isValid) {
this.Errors.has(Node) ?
this.Errors.get(Node)?.push(Result.message) :
this.Errors.set(Node,[Result.message]);
}
}
}
When the branches reach to the leaf mean GetFnNameAndArgs
method. You should have the real validator methods and a place to store them.
Also because we want to use string patterns in our schema we could have a Map
of string -> function
.
.
.
private Validators: Map<string,Function> = new Map();
private GetFnNameAndArgs(rules: any) {
return rules.split('|').map((item:any) => {
const V = item.split(':');
const name = V[0]; V.shift();
return { name, args: V };
});
}
And finally a place to store Validation Errors
.
.
.
private Errors: Map<string,string[]> = new Map();
.
.
.
To add any kind of validator function we simply use constructor
const Required = (value:any) => {
if(value) {
return {
isValid: true,
}
} else {
return {
isValid: false,
message:"Is Required"
}
}
}
class Validator {
constructor() {
this.Validators.set("Required", Required);
}
...
}
And finally add an entry point for your class to do validation for input objects.
class Validator {
...
Validate(Schema:any, Data:any){
this.Errors = new Map<string,string[]>();
this.MainValidator(Schema, Data);
return this.Errors;
}
...
}
Conclusion
Now you have a nice and clean validator, with some help of memoization
we also can make it faster than many other validator in js world.
You can find the whole code here.
Top comments (0)