npm package: @n7e/validation
Description: An extensible object validation library.
Author: Martin Pettersson
Latest version: 0.2.2

# Validation

An extensible object validation library.

For further insights, read on.

- [Installation](#installation)
- [Validator](#validator)
    - [Validation Strategy](#validation-strategy)
- [Validator Factory](#validator-factory)
    - [Default Validator Factory](#default-validator-factory)
- [Validator Builder](#validator-builder)
    - [Default Validator Builder](#default-validator-builder)
- [Rules](#rules)
    - [Rule Format](#rule-format)
    - [Available Rules](#available-rules)
    - [Custom Rules](#custom-rules)


## Installation

To install this library use your favorite package manager. No additional
steps are required to start using the library.

```shell
npm install @n7e/validation
```

*This library is implemented in TypeScript but can be used with JavaScript
without any additional steps.*


## Validator

The protagonist of this library is the validator. Validators lets you validate
arbitrary objects against a set of provided rules. All validators implement the
`Validator` interface. To reference it simply import it and use it as a type.

```typescript
import { Validator } from "@n7e/validation";

function doSomethingWith(validator: Validator): void {
    // ...
}
```

*`Validator` is just an interface describing the functionality of a validator.
To create validators use a [validator factory](#validator-factory) or
[validator builder](#validator-builder).*

To validate an object use the `validate` method:

```typescript
const rules = {
    key: "present"
};

validator.validate({key: "value"}, rules);
```

*The rules follow a [defined format](#rule-format).*

If the object is valid according to all the provided rules nothing happens.
However, if one or more rules are broken an `Issues` error is thrown. Depending
on the [validation strategy](#validation-strategy) being used the error contains
all or some of the broken rules with a short description of each issue.

To make things easy the `Issues` error provides an easy-to-read format when
serialized to JSON:

```json
{
    "key": [
        "Must be present"
    ]
}
```


### Validation Strategy

When validating an object one or more properties may break the given rules.
Whether the validator continues to check for additional issues and issues on
additional properties is defined by a `ValidationStrategy`.

A validation strategy can be passed to the `validate()` method when invoked. You
can also configure a validators default validation strategy using a
[validator builder](#validator-builder).

The available validation strategies are:

<dl>
    <dt><code>ValidationStrategy.STOP_AT_FIRST_INVALID_RULE</code></dt>
    <dd>Throws an exception at the first failed rule.</dd>
    <dt><code>ValidationStrategy.STOP_AT_FIRST_INVALID_PROPERTY</code></dt>
    <dd>Validates all rules for a property before throwing an exception if any of them failed.</dd>
    <dt><code>ValidationStrategy.RUN_ALL_RULES</code></dt>
    <dd>Runs all rules for all properties and throws an exception if any of them failed.</dd>
</dl>


## Validator Factory

To create a pre-configured validator instance you should use a
`ValidatorFactory`.

```typescript
import type { ValidatorFactory } from "@n7e/validation";

function someFunction(validatorFactory: ValidatorFactory): void {
    const validator = validatorFactory.createValidator();
    
    // ...
}
```

*`ValidatorFactory` is just an interface describing the functionality of a
validation factory. To create a validator factory instance you need to reference
a specific [implementation](#default-validator-factory).*


### Default Validator Factory

There's a provided default validator factory that creates a validator instance
with all [available rules](#available-rules).

```typescript
import { DefaultValidatorFactory } from "@n7e/validation";

const validator = new DefaultValidatorFactory().createValidator();
```


## Validator Builder

To configure and build a custom validator instance you should use a
`ValidatorBuilder`.

```typescript
import { ValidationStrategy, ValidatorBuilder } from "@n7e/validation";

function doSomethingWith(validationBuilder: ValidationBuilder): void {
    validatorBuilder
        .withRule(new SomeCustomRule())
        .withRule("custom-rule", (value, parameters) => {})
        .withDefaultStrategy(ValidationStrategy.STOP_AT_FIRST_INVALID_RULE);
}
```

A validator builder can be passed around and incrementally augmented to
eventually produce a desired validator.

```typescript
const validator = validatorBuilder.build();
```

*`ValidatorBuilder` is just an interface describing the functionality of a
validation builder. To create a validator builder instance you need to reference
a specific [implementation](#default-validator-builder).*


### Default Validator Builder

There's a provided implementation of `ValidatorBuilder` ready to use.

```typescript
import { DefaultValidatorBuilder } from "@n7e/validation";

const validatorBuilder = new DefaultValidatorBuilder();

// Add any desired rules and other configuration...

const validator = validatorBuilder.build();
```


## Rules

A rule is a single property criteria that must be adhered to. The simplest
example is the `present` rule that ensures that the property exists on the
object being validated.


## Rule Format

To define the rules a property needs to adhere to a defined format needs to be
followed:

```
           "type:string,array"
             ^        ^
             |        |
      Rule identifier |
                      |
              Optional parameters
```

*Any whitespace around rule parameters is trimmed.*

Rules are separated by a single `|` character. Here are some examples:

<dl>
    <dt>Single rule without parameters</dt>
    <dd><code>"present"</code></dd>
    <dt>Multiple rules without parameters</dt>
    <dd><code>"present|numeric"</code></dd>
    <dt>Single rule with a parameter</dt>
    <dd><code>"type:string"</code></dd>
    <dt>Single rule with multiple parameters</dt>
    <dd><code>"type:string,array,date"</code></dd>
    <dt>Multiple rules with parameters</dt>
    <dd><code>"present|type:string,array|min:5"</code></dd>
</dl>


## Available Rules

There's a provided set of available validation rules. Validators created with
the [default validator factory](#default-validator-factory) will include all
available validation rules.

- [present](#present)
- [type](#type)
- [numeric](#numeric)
- [min](#min)
- [max](#max)
- [uuid](#uuid)


### Present

Verifies that a property is present.

```typescript
// This will fail.
validator.validate({}, {key: "present"});
```

If a property is absent only the `present` rule will be checked so this will
pass:

```typescript
validator.validate({}, {key: "numeric"});
```


### Type

Verifies that values are of a certain type.

```typescript
// This will pass.
validator.validate({key: "value"}, {key: "type:string"});
```

The type can be any of the possible JavaScript typeof values:

- string
- number
- object
- function
- undefined
- boolean
- symbol
- bigint

Or any from the extended list below:

- array
- date
- null

Pass multiple types to verify that the value is one of many possible types:

```typescript
// This will pass.
validator.validate({key: "value"}, {key: "type:array,boolean,string"});
```


### Numeric

Verifies that values are numeric.

```typescript
// This will pass.
validator.validate({key: "5"}, {key: "numeric"});
```


### Min

Verifies that values are at least a given value.

```typescript
// This will pass.
validator.validate({key: 5}, {key: "min:5"});
```

*If the value is a string, a collection (array, set, map etc. basically any
object with a `length` or `size` property) or an iterator the size of the value
will be compared to the minimum value. In any other case the value will be
compared as is.*

```typescript
// This will fail since the length of the string is 1.
validator.validate({key: "7"}, {key: "min:5"});
```


### Max

Verifies that values are at most a given value.

```typescript
// This will pass.
validator.validate({key: 5}, {key: "max:5"});
```

*If the value is a string, a collection (array, set, map etc. basically any
object with a `length` or `size` property) or an iterator the size of the value
will be compared to the minimum value. In any other case the value will be
compared as is.*

```typescript
// This will pass since the length of the string is 1.
validator.validate({key: "7"}, {key: "max:5"});
```


### UUID

Verifies that values are UUIDs.

```typescript
// This will pass.
validator.validate({key: "00000000-0000-0000-0000-000000000000"}, {key: "uuid"});
```

*This rule accepts nil UUIDs.*


## Custom Rules

You can easily add your own rules and use them using the
[defined format](#rule-format). To do so either create a
[rule class](#rule-class) or a [rule function](#rule-function) and register it
using a [validator builder](#validator-builder).


### Rule Class

Create a class that implements the `Rule` interface (This will work with
JavaScript as well, just omit the types):

```typescript
class CustomRule implements Rule {
    public readonly identifier = "custom-rule";
    
    public check(value: unknown, parameters: Array<string>): void {
        // If the value is invalid throw an Issue error with a short description
        // of the issue, otherwise do nothing.
    }
}
```

To make it available to the validator register the rule using a
[validator builder](#validator-builder):

```typescript
const validator = validatorBuilder
    .withRule(new CustomRule())
    .build();

validator.validate({}, {key: "custom-rule"});
```


### Rule Function

Provide an identifier and a function to a
[validator builder](#validator-builder) to make the rule available to the
validator:

```typescript
const validator = validatorBuilder
    .withRule("custom-rule", (value, parameters) => {
        // If the value is invalid throw an Issue error with a short description
        // of the issue, otherwise do nothing.
    })
    .build();

validator.validate({}, {key: "custom-rule"});
```