DEV Community

Matheus Carvalho
Matheus Carvalho

Posted on

Typescript: Creating Reusable Validations with Light Validate

Considering that when we talk about programming, much of the code developed by developers is equivalent to data validations, in this article I will look at ways to apply code quality concepts such as reuse, code reduction, and clean code through the light-validate library on client / server layers using Typescript as the programming language.

About Light Validate

https://www.npmjs.com/package/light-validate

Light Validate is a library that allows the developer, to create data validations, and to use them in model class mapping through Annotations / Decorators to automate the miscellaneous data validation routine, allowing for a similar implementation to existing implementations. used in Java languages through HIBERNATE / SPRINGDATA, or C # through EntityFramework or similar libraries.

Some important features:

  • Framework Agnostic.
  • Functional Paradigm.
  • Asynchronous Paradigm.

Conceptual

Creating Validations (Light Rules)

Validations that can be interpreted by Light Validate are basically functions that implement the Light Rule interface.

LightRule Interface

The LightRule interface represents a void Function that receives 2 parameters, which are value, and target.

  • Value Parameter, this parameter represents the value that will be validated.
  • Target Parameter, this parameter represents the object containing the value parameter.   If the value to be validated is invalid, the function must throw via throw a code, or error message. If the value to be validated is valid, the function should neither post nor return anything.

Creating Mappings (Light Mapping)

Mappings that are interpreted by Light Validate are basically classes, with decorators LightValidate in decorating properties whose values ​​are to be validated.

Decorator LightValidate

Decorator LightValidate is basically a function that receives as parameters the validations that should validate the property whose is decorated with decorator LightValidate.

Validating Objects

To validate an object, Light Validate provides some functions, which are basically functions that take as parameters, target and Klass, and return a promise.
If there are any errors, the promise will be rejected with a vector object that implements the interface LightException

  • Target Parameter, this parameter represents the object that will be validated.
  • Klass Parameter, this parameter represents the mapping that should be used to validate the Target parameter.

Interface Light Exception

The Interface has 4 properties, aiming to represent exception data generated by validation, which are as follows:

  • rule, The Rule that generated the exception.
  • target, The Object that was validated and raised the exception.
  • property, The property whose value raised the exception.
  • code, The Code thrown by Rule as an exception.

Validate Function

The validate function is used to validate only 1 or multiple objects from a mapping.

validateOne Function

The validate function is used to validate only 1 object from a mapping.

validateEach Function

The validate function is used to validate multiple objects from one mapping.

In practice

Let's consider the following concept to show in practice how the library works:

You must create a screen that enrolls Students in an Educational Institution.

The Student Template, which will be sent to the server, is represented by the following interface:

/* student.model.ts */
export  interface  StudentModel {
    name:string;
    document:string;
    birthDate:string;
    phone:string;
    state:string;
    country:string;
}

Consider that the system requester specifies that the fields must have the following validations

  • name: must not have numeric characters, must have at least 3 characters, and at most 40 characters.
  • document: must have 11 characters.
  • birthDate: must have the format ## / ## / ####.
  • phone: Must allow 9 digit phone number.
  • state: must have only one of the following values: Sao Paulo, Minas Gerais, or Rio de Janeiro, New York, Hollywood, Kansas
  • country: must have only one of the following values: Brazil for if the state field is equivalent to São Paulo, Minas Gerais or Rio de Janeiro, and United States for the others.

Installation

Run the following commands for installing the library:

$ npm install -save light-validate
$ npm install -save reflect-metadata

Creating Validations (Light Rules)

Field Name: NameLightRule

/* name.light-rule.ts */
import { LightRule } from 'light-validate';
export  const  NameLightRule:LightRule = async  function (value:string, target:any) {
    if(value){
        if(!new  RegExp("/^[A-Za-z\s]+$/").test(value)){
            throw  'only-alphabetical-characters';
        }
        if(value.length<3) {
            throw  'min-3-characters';
        }
        if(value.length>40) {
            throw  'max-40-characters';
        }
    }
}

Field Document: DocumentLightRule

/* document.light-rule.ts */
import { LightRule } from 'light-validate';
export  const  DocumentLightRule:LightRule = async  function (value:string, target:any) {
    if(value) {
        if(value.length!=11) {
            throw  'must-be-11-numbers';
        }
    }
}

Field BirthDate: BirthDateLightRule

/* birth-date.light-rule.ts */
import { LightRule } from 'light-validate';
export  const  BirthDateLightRule:LightRule = async  function (value:string, target:any) {
    const  values = value.split('/');
    if(values.length!=3) {
        throw  'invalid-date';
    }
    if(values[0].length==2) {
        throw  'invalid-date';
    }
    if(values[1].length==2) {
        throw  'invalid-date';
    }
    if(values[2].length==4) {
        throw  'invalid-date';
    }
}

Field Phone: PhoneLightRule

/* phone.light-rule.ts */
import { LightRule } from 'light-validate';
export  const  PhoneLightRule:LightRule = async  function (value:string, target:any) {
    if(value.length!==9){
        throw  'invalid-phone';
    }
}

Field State: StateLightRule

/* state.light-rule.ts */
import { LightRule } from 'light-validate';
export  const  StateLightRule:LightRule = async  function (value:string, target:any) {
    if(['São Paulo','Minas Gerais','Rio de Janeiro','New York','Hollywood','Kansas'].indexOf(value)===-1){
        throw  'invalid-state';
    }
}

Field Country: CountryLightRule

/* country.light-rule.ts */
import { LightRule } from 'light-validate';
export  const  CountryLightRule:LightRule = async  function (value:string, target:any) {
    if(value==='Brasil' && ['São Paulo','Minas Gerais','Rio de Janeiro'].indexOf(target['state'])===-1){
        throw  'invalid-country';
    }
    if(value==='United States' && ['New York','Holywood','Kansas'].indexOf(target['state'])===-1){
        throw  'invalid-country';
    }
}

Creating Mappings (Light Mapping)

Below is the class that will represent validation mapping to validate objects that follow its format.

/* student.light-mapping.ts */
import { StudentModel } from  './student.model';
import { NameLightRule } from  './name.light-rule';
import { DocumentLightRule } from  './document.light-rule';
import { BirthDateLightRule } from  './birth-date.light-rule';
import { PhoneLightRule } from  './phone.light-rule';
import { StateLightRule } from  './state.light-rule';
import { CountryLightRule } from  './country.light-rule';
import { LightValidate } from 'light-validate';

export  class  StudentLightMapping  implements  StudentModel {

    /*
        All properties that are decorated with the @LightValidate decorator
        must be initialized with undefined, or null, otherwise they will be ignored.
    */

    @LightValidate(NameLightRule)
    public name:string = undefined;

    @LightValidate(DocumentLightRule)
    public document:string = undefined;

    @LightValidate(BirthDateLightRule)
    public birthDate:string = undefined;

    @LightValidate(PhoneLightRule)
    public phone:string = undefined;

    @LightValidate(StateLightRule)
    public state:string = undefined;

    @LightValidate(CountryLightRule)
    public country:string = undefined;

}

Validando Objetos

import { StudentModel } from  './student.model';
import { StudentLightMapping } from  './student.light-mapping';
import { validate } from 'light-validate';

const model: StudentModel = {
    name:'John Deer',
    document:'444111222',
    birthDate:'2019-12-12',
    phone:'111133',
    state:'New York',
    country:'Brazil'
}

validate(model,StudentLightMapping)
    .then(()=>console.log('No exceptions found'))
    .catch((exceptions:LightException[])=> 
                exceptions.forEach((exception:LightException)=>
                    console.log(`validation ${exception.rule}, target object ${e.target}, property ${exception.property}, code ${e.code}`)
                )
            )
        );

Creating Reusable Validations

As you can see, all the validations I created in this guide can be optimized and separated so that they can be reused, such as:

NameLightRule broken into 3 validations:

OnlyAlphaLightRule, which validates a field that should only receive alphabetic characters.

/* only-alpha.light-rule */
import { LightRule } from 'light-validate';

export const OnlyAlphaLightRule:LightRule = async function (value:string, target:any) {*/
    if(value){
        if(!new  RegExp("/^[A-Za-z\s]+$/").test(value)){
            throw  'only-alphabetical-characters';
        }
    }
}

MinLengthLightRule, which validates a field that must receive a value that has at least X characters.

/* min-length.light-rule */
export const MinLengthLightRule:LightRule = function(length:number) {
    return async  function (value:string, target:any) {*/
        if(value && value.length<length) {
            throw  `min-${length}-characters`;
        }
    }
}

MaxLengthLightRule, which validates a field that must receive a value that has a maximum of X characters.

/* max-length.light-rule */
export const MaxLengthLightRule:LightRule = function(length:number) {
    return async  function (value:string, target:any) {*/
        if(value && value.length>length) {
            throw  `max-${length}-characters`;
        }
    }
}

Thus, we have already been able to eliminate 3 specific validations through the use of 3 generic validations, which can be reused in various situations.

import { StudentModel } from  './student.model';
import { OnlyAlphaLightRule } from  './only-alpha.light-rule';
import { MinLengthLightRule } from  './min-length.light-rule';
import { MaxLengthLightRule } from  './max-length.light-rule';
import { BirthDateLightRule } from  './birth-date.light-rule';
import { StateLightRule } from  './state.light-rule';
import { CountryLightRule } from  './country.light-rule';
import { LightValidate } from 'light-validate';

export  class  StudentLightMapping  implements  StudentModel {

    /*
        All the properties that most adorn with the @LightValidate decorator
        must be initialized to undefined or null otherwise they will be ignored.
    */

    @LightValidate(OnlyAlphaLightRule,MinLengthLightRule(3),MaxLengthLightRule(40))
    public name:string = undefined;

    @LightValidate(MinLengthLightRule(11),MaxLengthLightRule(11))
    public document:string = undefined;

    @LightValidate(BirthDateLightRule)
    public birthDate:string = undefined;

    @LightValidate(MinLengthLightRule(9),MaxLengthLightRule(9))
    public phone:string = undefined;

    @LightValidate(StateLightRule)
    public state:string = undefined;

    @LightValidate(CountryLightRule)
    public country:string = undefined;

}

Using with SPA Frameworks

The Light Validate library has some abstractions for some industry frameworks that provide support for Typescript, which are:

These are basically policy modules that communicate with the Light Validate library to perform validations, and display exceptions similar to Jquery Validate.

Top comments (0)