@bavaria2tm/validator v1.0.2
Validators
Description
The validation of data is an essential part of an application, used in
api endpoints, input data validation, ... just to name a few examples. Most
commonly in nodejs the express validators, joi validators, or
class validators are used.
In our case we needed something with type save validation similar to
class validators but combined with the method chaining and flexibility of
joi validators.Furthermore we need to use the
validators in frontend and backend and have to be therefor independent of a
library. Because of all of these reasons we decided to create our on validators.
Links
Class Validators
Joi Validators
Express Validator
What is a Class Schema in our Use Case?
A class schema is a TypeScript class that serves as a
blueprint for the data we send or receive between different parts of our
application, such as between the client and server. Beyond just describing the
shape of the data, our Validators also have built-in
rules for validating and transforming this data. This ensures that the data is
not only correctly formatted but also meets specific conditions before we
process it further. Think of it as a smart data package that knows how to
check itself for errors and even correct some of them automatically.
Usage as Class Schema's
The advantages of Class Schemas are that they have strict type safety and
are cleaner then the object validators in highting in the IDE (class
definitions pop out more then constants)
1. Define a Schema Class
class UserSchema extends ValidatorSchema<IUser> {
//0 - validate the name, which is MANDATORY
// > Note: by default all fields are "REQUIRED"
name = Validator.String()
.isNotEmpty()
.minLength(5)
.hasNoSpace() // example of VALIDATORS
.trim()
.toLowerCase()
.encodeBase64(); // example of TRANSFORMERS
//1 - OPTIONAL field
// > Note: alternatively use ".optionalIf((o: IUser) => ...)"
nickName = Validator.String().optional();
//2 - set DEFAULT value
age = Validator.Number().isPositive().default(-1);
//3 - conditional "IF" validation
adult = Validator.Boolean().validateIf((value: boolean) => adult);
//4 - validate an ARRAY, of strings
friends = Validator.Array()
.isNotEmpty()
.maxLength(10)
.shuffle()
.each(Validator.String().trim());
//5 - validate an OBJECT, external class schema
contact = Validator.Object().hasNoNestedObjects().inspect(ContactSchema); // nested object of "IAddress"
//7 - validate an ARRAY, of OBJECTS
friendContacts = Validator.Array().isNotEmpty().inspectEach(ContactSchema);
}
class ContactSchema extends ValidatorSchema<IContact> {
phone = Validator.String().isNotEmpty();
email = Validator.String().isNotEmpty().isEmail();
}2. Validate By Class Schema
Use the ValidatorSchema that was created before to validate the data with
one of the below approaches.
//0 - validate by boolean
const isValid: boolean = Validator.Schema.isValid(data, UserSchema);
if (isValid) console.log(" > user is valid");
else console.log(" > user is invalid");
//1 - validate with getting the detailed infos
// > interface IValidatorError {
// > property : string;
// > value : boolean;
// > errors : string[];
// > children : IValidatorError[];
// > }
const validationErrors: IValidatorError[] = Validator.Schema.validate(
data,
UserSchema
);
console.log(" > validationErrors: ", validationErrors);Note: the
transformersare alsoexecuted during the validation
3. Process By Class Schema
Use the UserSchema that was created before to process / clean the data based
on the transformers.
// process the user data by the schema
// > Note: this will execute logic like trim values or apply default values,
// > and more. It will return THROW an ERROR for an invalid object.
const userData: IUser = Validator.Schema.transform(requestBodyData, UserSchema);Note: the
validationis alsoexecuted during the processing, so if the data is invalid we get the error
Usage of Object Schemas
The advantages of schemas are, that they give high type safety (it forces
us to have a validator for each value and forces the exact validator type).
The disadvantage is we can only have a single validation chain for each value.
Note: use the
Schema validationforconfigsand otherpartial inline validations
1. Define a Object Schema
const userSchema: ValidatorSchema<IUser> = {
//0 - simple validators
name: Validator.String().isNotEmpty().trim().toLowerCase(),
nickName: Validator.String().optional(),
//1 - validate an ARRAY, of strings
friends: Validator.Array()
.each(Validator.String().trim())
.isNotEmpty()
.maxLength(10)
.shuffle(),
//2 - validate an OBJECT, as INLINE OBJECT
address: Validator.Object()
.inspect({
phone: Validator.String().toLowerCase(),
email: Validator.String().toLowerCase()
})
.hasNoNestedObjects(),
//3 - validate an OBJECT, as INLINE
contact: {
phone: Validator.String().toLowerCase(),
email: Validator.String().toLowerCase()
}
};Note: the
schemascan haveinline objects / nested validators, but youcan not have a child schema
2. Validate By Object Schema
Use the Schema that was created before to validate the data with one of the
below approaches.
//0 - validate by boolean
const isDataValid: boolean = Validator.Schema.isValid(data, userSchema);
if (isDataValid) console.log(" > user is valid");
else console.log(" > user is invalid");
//1 - validate with getting the detailed infos
const validationErrors: IValidatorError[] = Validator.Schema.validate(
data,
userSchema
);
console.log(" > validationErrors: ", validationErrors);
//2 - validate inline
const validationErrors: IValidatorError[] = Validator.Schema.validate(data, {
name: Validator.String().isNotEmpty().trim().toLowerCase(),
nickName: Validator.String().optional()
// ... add other validators
});Note: the
transformersare alsoexecuted during the validation
3. Process By Object Schema
Use the objectSchema that was created before to process / clean the data
based on the transformers.
//0 - process the user data by the schema
// > Note: this will execute logic like trim values or apply default values,
// > and more. It will return THROW an ERROR for an invalid object.
const userData: IUser = Validator.Schema.transform(requestBodyData, userSchema);
//1 - transform inline
const validationErrors: IValidatorError[] = Validator.Schema.transform(data, {
name: Validator.String().isNotEmpty().trim().toLowerCase(),
nickName: Validator.String().optional()
// ... add other validators
});Note: the
validationis alsoexecuted during the processing, so if the data is invalid we get the error
Other Usages
1. Direct Execution
The validators can also be executed directly
//0 - validate
const isValid: boolean = Validator.String()
.isNotEmpty()
.isLowerCase()
.isValid("My Cat");
//1 - processing value
const processedValue: string = Validator.String()
.isNotEmpty()
.toLowerCase()
.process("My Cat");2. Use Validators for Angular FormControl
The validators can even be used with Angular FormGroup / FormControls and can
provide the following standard required by angular
// standard required by angular
export function passwordValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const password: string = control.value; // ... check "control.value"
return this.isValidPassword(password)
? { weekPassword: "Password is week" }
: null;
};
}To convert the validator into a FormControls Validator use the approach
below
//0 - get the validator as an wrapped "angular validator"
// > Note: returns "(control: AbstractControl): ValidationErrors | null"
const angularValidator: ValidatorFn = Validator.String().isPassword().ngForm();
//1 - use the validator
const signupForm: FormGroup<ILoginForm> = new FormGroup<ILoginForm>({
password: new FormControl<string>("", angularValidator),
name: new FormControl<string>("", Validator.String().isNotEmpty().ngForm()) // use it directly inline
// ...
});Appendix
Appendix A: Validation Result
The validation returns the following result. Be aware that arrays and
objects can have children.
interface IValidatorError {
property: string;
value: boolean;
errors: string[];
children: IValidatorError[];
}If the following example data is validated, it would give the following result below.
// data which has invalid entries
const data = {
name: "Yoda",
age: -1, // negative number is invalid
hobbies: ["swimming", undefined, "running"], // undefined is invalid
address: {
country: null, // null is not a valid country
zipCode: 45678
}
};
// validation result
const validationResult: Partial<IValidatorError>[] = [
{
property: "age",
value: -1,
errors: ["No negative value"],
children: []
},
{
property: "hobbies",
value: ["swimming", undefined, 22, "running"],
errors: ["Some items are invalid"],
children: [
{
property: "1", // index in the array
value: undefined,
errors: ["Item is undefined"],
children: []
}
]
},
{
property: "address",
value: { country: undefined, zipCode: 45678 },
errors: ["Object is invalid"],
children: [
{
property: "country", // property of the object
value: null,
errors: ["Has to be a valid string"],
children: []
}
]
}
];Appendix B: Advanced Usage
The below example is advanced and can be used as following: lets assume the
frontend is sending a user's password in an encrypted format, then the below
validator would be executed as following.
Check if we got any string value
isNotEmpty()Decrypt the received password
decryptASE128('9sdsdafsdafafh8asdsdafsdaffh9h89')Validate if the password is secure enough
isPassword()Hash the password, as we only need the hash to compare it
transform((value: string) => Util.Hash(value))
class UserSchema extends ValidatorSchema<IUser> {
password = Validator.String()
.isNotEmpty()
.decryptASE128("9sdsdafsdafafh8asdsdafsdaffh9h89")
.isPassword()
.map((value: string) => Util.Hash(value));
}Appendix C: Validator Utility
The Validator Utility is a handy tool that offers simple and effective validation for various entities such as emails, domains, passwords, etc. It provides two core functions for handling validation tasks, allowing you to either simply check validity or obtain the specific reasons for validation failures.
Each validator has the following two methods
isValid(entity: string): boolean, use this function when you only need to know if an entity is valid or not. The function returns a boolean value.if (Validator.Email.isValid(email)) { console.log("Email is valid!"); } else { console.log("Email is invalid!"); }validate(entity: string): string[], use this function when you need to know the reasons for validation failure, such as when you need to display specific feedback to the user. The function returns an array of strings that describe the validation errors.//0 - validate the mail const errors: string[] = Validator.Email.validate(email); //1 - show the errors if (errors.length === 0) { console.log("Email is valid!"); } else { console.log("Email is invalid for the following reasons: ", errors); }
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago