Typescript: Creating Reusable Validations with Light Validate
Tags: typescript, lightvalidate, codequality, validation
Published: 2019-10-14 14:54:00

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:
```typescript
/* 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**
```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  'only-alphabetical-characters';
		}
		if(value.length<3) {
			throw  'min-3-characters';
		}
		if(value.length>40) {
			throw  'max-40-characters';
		}
	}
}
```
Field 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  'must-be-11-numbers';
		}
	}
}
```
Field 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  '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**
```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  'invalid-phone';
	}
}
```

Field 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  'invalid-state';
	}
}
```
Field 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  '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.
```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 {
	
	/*
		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
```typescript
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.
```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  'only-alphabetical-characters';
		}
	}
}
```

**MinLengthLightRule**, which validates a field that must receive a value that has at least X characters.
```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  `min-${length}-characters`;
		}
	}
}
```

**MaxLengthLightRule**, which validates a field that must receive a value that has a maximum of X characters.
```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  `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.
```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 {
	
	/*
	    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:

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

These are basically policy modules that communicate with the Light Validate library to perform validations, and display exceptions similar to [**Jquery Validate**](https://jqueryvalidation.org/).