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)