Typescript : Criando Validações Reutilizáveis com Light Validate
Tags: typescript, lightvalidate, codequality, validation
Published: 2019-10-14 03:23:30

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 :
```typescript
/* 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**
```typescript
/* 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**
```typescript
/* 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**
```typescript
/* 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**
```typescript
/* 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**
```typescript
/* 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**
```typescript
/* 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.
```typescript
/* 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
```typescript
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.
```typescript
/* 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.
```typescript
/* 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.
```typescript
/* 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.
```typescript
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 :

- [**Angular**](https://www.npmjs.com/package/light-validate-angular-ui)
- [**Vue** ](https://www.npmjs.com/package/light-validate-vue-ui)

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** ](https://jqueryvalidation.org/).