# Validators

There are two ways to define validation rules:
 - with `ModelValidator`, wich use decorators
 - with `SchemaValidator`, wich use plain object

Both of them extends `AbstractValidator`, that provides common interface.

## AbstractValidator

### Public interface

Class require to implement two methods:
 - Getter for name of model/schema
```ts
public get modelName(): string;
```
 - Validate method
```ts
public validate: async (groups?: Array<string>) => Promise<void>
```

And provide implemented methods:
 - List of attributes of provided model/schema
```ts
public get modelAttributes(): Array<string>
```
 - Object with attribte name: value
```ts
public get modelValues(): {[key: string]: string | boolean | number}
```
 - Object with attribute name: error description
 ```ts
 public get modelErrors():  {[key: string]: string}
 ```
 - Method for setting model value
```ts
public setModelValue: (attribute: string, value: string | boolean | number) => void;
```
 - Method for setting default values
```ts
public setDefaults: (defaults: {[key: string]: string | boolean | number}) => void;
```
 - Method for rolling back to settled defaults
```ts
public dropToDefaults: () => void;
```
 - Method for clear all values
```ts
public clear: () => void;
```
 - Method for manual adding errors
 ```ts
 public addErrors: (errors: errors: Array<{ attribute: string, details: string }>) => void;
 ```

### Internal interface

Class have two protected containers:
 - Container for errors
```ts
protected modelErrorsContainer: Map<string, Array<{ attribute: string, details: string }>>
```
 - Container of model/schema
```ts
protected modelContainer: {
    instance: {[key: string]: string | boolean | number};
    defaults: {[key: string]: string | boolean | number};
}
```

`INSTANCE` - object or class that describe attributes and rules.
*`ALL ATTRIBUTES SHOULD BE INITIALIZED, EVEN WITH UNDEFINED VALUE!`*

#### WRONG:
```ts
class SomeModel {
    public someField: string;
}
```

#### RIGHT:
```ts
class SomeModel {
    public someField: string = undefined; // or null, string, number, boolean.
}
```

`ERRORS CONTAINER` - "Map" instance, that contains failed results of validation. Key of "Map" is group name, value is array of attributes and error decription.

Class provide `handleErrors` helper for handling errors:
```ts
protected handleErrors: (errors, groups) => void;
```
 - `errors` - array of errors, that generated by `ClassValidator.validate` method.
 - `groups` - array of groups

So, correct usage will be:
```ts
public validate = async (groups?: Array<string>): Promise<void> => {
    const errors = await ClassValidator.validate(/*SOME_ARGUMENTS*/);

    this.handleErrors(errors, groups);
}
```

For correct work of validator, there should be created correct instance and defaults. All public method depends on two internal methods.




## ModelValidator

```ts
import * as ClassValidator from "class-validator"
import { ModelValidator } from "react-formawesome-core";

class UserModel {
    @ClassValidator.MinLength(4, {
        groups: ["name", "personal"]
    })
    public name: string = undefined;

    @ClassValidator.MinLength(4, {
        groups: ["surname", "personal"]
    })
    public surname: string = undefined;

    @ClassValidator.IsNumberString( {
        groups: ["address"]
    })
    public address: string = undefined;
}

// Create instance and provide model class and defaults
const validator = new ModelValidator(UserModel, {
    name: "John",
    surname: "-",
    address: "LA"
});
```

## SchemaValidator

```ts
import { SchemaValidator } from "react-formawesome-core";

const UserSchema = {
    name: "UserSchema",
    properties: {
        name: [
            {
                type: "minLength",
                constraints: [4],
                groups: ["name", "personal"]
            }
        ],
        surname: [
            {
                type: "minLength",
                constraints: [4],
                groups: ["surname", "personal"]
            }
        ],
        address: [
            {
                type: "isNumberString",
                groups: ["address"]
            }
        ]
    }
};

// Create instance and provide schema and defaults
const validator = new SchemaValidator(UserSchema, {
    name: "John",
    surname: "-",
    address: "LA"
});
```

## Error cases
```ts
// For both of validators
validator.setDefaults({}) // Throw error: defaults is an empty object
validator.setDefaults("") // Throw error: defaults is not an object
validator.setDefaults(() => undefined) // Throw error: defaults is not an object

validator.setModelValue(/*Not string value*/, false) // Throw error: attribute is not a string
validator.setModelValue("unexpectedAttribute", false) // Throw error: attribute is not exist in model/schema
validator.setModelValue("name", {}) // Throw error: value is not a string
validator.setModelValue("name", () => undefined) // Throw error: value is not a string

validator.addErrors(/*Not array value*/) // Throw error: error is not an array
validator.addErrors([{attribute: /*Not string value*/}]) // Throw error: attribute is not a string
validator.addErrors([{attribute: "unexpectedAttribute"}]) // Throw error: attribute is not exist in model/schema
validator.addErrors([{attribute: "name", details: /*Not string value*/}]) // Throw error: details is not a string

(async () => {
    await validator.validate(["not defined group"]) // Throw error: Group does not defined in model/schema
})();

// For ModelValidator
new ModelValidator({}) // Throw error: Model is not a function
new ModelValidator("") // Throw error: Model is not a function
new ModelValidator(CorrectModel, "") // Throw error: defaults is not an object
new ModelValidator(CorrectModel, {}) // Throw error: defaults is empty
new ModelValidator(CorrectModel, { nonExistField: "" }) // Throw error: nonExistField does not exist in Model


// For SchemaValidator
new SchemaValidator({}) // Throw error: name is undefined
new SchemaValidator({name: /*Non string value*/}) // Throw error: name is not a string
new SchemaValidator({name: "Ok"}) // Throw error: properties is undefined
new SchemaValidator({name: "Ok", properties: /*Non object*/}) // Throw error: properties is not an object
new SchemaValidator(CorrectSchema, "") // Throw error: defaults is not an object
new SchemaValidator(CorrectSchema, {}) // Throw error: defaults is empty
new SchemaValidator(CorrectSchema, { nonExistField: "" }) // Throw error: nonExistField does not exist in Schema

```


### Validator config

Allow configure the validator behavior

```ts
{
    skipAttributeCheck?: boolean; // do not throw error when passed attribute does not exist in the model/schema
}
```

```ts
new SchemaValidator(CorrectSchema, undefined, { skipAttributeCheck: true });
new ModelValidator(CorrectModel, undefined, { skipAttributeCheck: true });
```
