loading...

Typescript : Criando Validações Reutilizáveis com Light Validate

minatonda profile image Matheus Carvalho ・7 min read

Considerando que, quando falamos de programação, grande parte do código desenvolvido pelos desenvolvedores são equivalentes a validações de dados, nesse artigo buscarei abordar maneiras de aplicar conceitos de qualidade de código tais quais como reutilização, redução de código, e código limpo através da biblioteca light-validate em camadas client/server que utilizam Typescript como linguagem de programação.

Sobre o Light Validate

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

Light Validate é uma biblioteca que permite ao desenvolvedor, criar validações de dados, e utilizar as mesmas em mapeamento de classes modelo através de Annotations / Decorators para automatizar a rotina de validação de dados diversos, permitindo uma implementação semelhante as implementações já utilizadas nas linguagens Java através do HIBERNATE/SPRINGDATA, ou C# através do EntityFramework ou bibliotecas semelhantes.

Algumas características importantes:

  • Agnóstico a Framework.
  • Paradigma Funcional.
  • Paradigma Assíncrono.

Conceitual

Criando Validações (Light Rules)

As Validações que podem ser interpretadas pelo Light Validate, são basicamente funções que implementam a interface Light Rule.

Interface LightRule

A interface LightRule representa uma Função void que recebe 2 parâmetros, que são value, e target.

  • Parâmetro Value, esse parâmetro representa o valor que será validado.
  • Parâmetro Target, esse parâmetro representa o objeto que contem o parâmetro value.

Caso o valor a ser validado seja inválido, a função deve lançar via throw um código, ou mensagem de erro.
Caso o valor a ser validado seja válido, a função não deve lançar e nem retornar nada.

Criando Mapeamentos (Light Mapping)

Os Mapeamentos que são interpretados pelo Light Validate, são basicamente classes, com decorators LightValidate em decorando as propriedades cujo os valores deverão ser validados.

Decorator LightValidate

O Decorator LightValidate é basicamente uma função que recebe como parâmetros, as validações que deverão validar a propriedade cujo é decorada com o decorator LightValidate.

Validando Objetos

Para validar um objeto, o Light Validate disponibiliza algumas funções, que basicamente são funções que recebem como parâmetros, target e Klass, e retornam uma promise.
Caso haja algum erro, a promise será rejeitada com um vetor objetos que implementam a interface LightException

  • Parâmetro Target, esse parâmetro representa o objeto que será validado.
  • Parâmetro Klass, esse parâmetro representa o mapeamento que deverá ser utilizado para a validação do parâmetro Target.

Interface Light Exception

A Interface apresenta 4 propriedades, visando representar dados da exceção gerada pela validação, que são as seguintes :

  • rule , A Rule que gerou a exceção.
  • target, O Objeto que foi validado e gerou a exceção.
  • property, A propriedade cujo o valor gerou a exceção.
  • code, O Código lançado pela Rule como exceção.

Função validate

A função validate, é utilizada para validar apenas 1 ou vários objetos a partir de um mapeamento.

Função validateOne

A função validate, é utilizada para validar apenas 1 objeto a partir de um mapeamento.

Função validateEach

A função validate, é utilizada para validar vários objetos a partir de um mapeamento.

Na prática

Vamos considerar o seguinte conceito para mostrar na prática como a biblioteca funciona :

Você deve criar uma tela que efetua cadastro de Alunos em uma Instituição Educacional.

O Modelo Aluno, que será enviado para o servidor, é representado pela seguinte interface :

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

Considerar que o requisitante do sistema especifica que os campos devem possuir as seguintes validações

  • name : não deve possuir caráteres numéricos, deve possuir ao menos 3 caracteres, e no máximo 40 carácteres.
  • document : deve possuir 11 carácteres.
  • birthDate : deve possuir o formato ##/##/####.
  • phone : deve permitir número de telefone de 9 dígitos.
  • state : deve possuir apenas um dos seguintes valores : São Paulo, Minas Gerais ou Rio de Janeiro, New York, Hollywood, Kansas
  • country : deve possuir apenas um dos seguintes valores : Brasil para caso o campo state seja equivalente a São Paulo, Minas Gerais ou Rio de Janeiro, e United States para os demais.

Instalação

Executar os seguintes comandos para a instalação da biblioteca :

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

Criando Validações (Light Rules)

Campo 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  'apenas-caracteres-alfabeticos';
        }
        if(value.length<3) {
            throw  'ao-menos-3-caracteres';
        }
        if(value.length>40) {
            throw  'ao-maximo-40-caracteres';
        }
    }
}

Campo 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  'deve-possuir-11-numeros';
        }
    }
}

Campo 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  'data-invalida';
    }
    if(values[0].length==2) {
        throw  'data-invalida';
    }
    if(values[1].length==2) {
        throw  'data-invalida';
    }
    if(values[2].length==4) {
        throw  'data-invalida';
    }
}

Campo 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  'telefone-invalido';
    }
}

Campo 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  'estado-invalido';
    }
}

Campo 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  'pais-invalido';
    }
    if(value==='United States' && ['New York','Holywood','Kansas'].indexOf(target['state'])===-1){
        throw  'pais-invalido';
    }
}

Criando Mapeamentos (Light Mapping)

Abaixo , a classe que representará o mapeamento de validações para validar objetos que sigam o formato da mesma.

/* 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 {

    /*
        Todas as propriedades que forem decoradas com o decorator @LightValidate
        devem ser inicializadas com undefined, ou null, caso o contrário, serão ignoradas.
    */

    @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:'João da Silva Santos',
    document:'444111222',
    birthDate:'2019-12-12',
    phone:'111133',
    state:'New York',
    country:'Brazil'
}

validate(model,StudentLightMapping)
    .then(()=>console.log('Nenhuma exceção foi encontrada'))
    .catch((exceptions:LightException[])=> 
                exceptions.forEach((exception:LightException)=>
                    console.log(`Exceção gerada pela Validação ${exception.rule}, utilizando como objeto alvo o objeto ${e.target}, validando o valor da propriedade ${exception.property}, com o código lançado equivalente a ${e.code}`)
                )
            )
        );

Criando Validações Reutilizáveis

Como podem ver, todas as validações que criei nesse guia, podem ser otimizadas e separadas de maneira que possam ser reutilizadas, como por exemplo:

NameLightRule quebrada em 3 validações :

OnlyAlphaLightRule, que valida um campo que deve receber apenas caracteres alfabéticos.

/* 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  'apenas-caracteres-alfabeticos';
        }
    }
}

MinLengthLightRule, que valida um campo que deve receber um valor que tenha no mínimo X carácteres.

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

MaxLengthLightRule, que valida um campo que deve receber um valor que tenha no máximo X carácteres.

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

Dessa forma, já conseguimos eliminar 3 validações específicas, através da utilização de 3 validações genéricas, que podem ser reutilizadas em diversas situações.

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 {

    /*
        Todas as propriedades que forem decoradas com o decorator @LightValidate
        devem ser inicializadas com undefined, ou null, caso o contrário, serão ignoradas.
    */

    @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;

}

Utilizando com Frameworks SPA

A biblioteca Light Validate possuí algumas abstrações para alguns frameworks de mercado que fornecem suporte para Typescript, que são elas :

São basicamente, módulos de diretivas que se comunicam com a biblioteca Light Validate para executar validações, e exibir as exceções de forma semelhante ao Jquery Validate .

Posted on by:

minatonda profile

Matheus Carvalho

@minatonda

Searching for development techniques to produce quality, scalable and productive code to avoid fatigue ... Lazy programmers are the best according to Bill Gates (I think).

Discussion

markdown guide