README.md

# Valid

![CI](https://github.com/sporto/gleam-valid/workflows/test/badge.svg?branch=main)

A validation library for [Gleam](https://gleam.run/).

API Docs: <https://hexdocs.pm/valid>.

This library follows the principle [Parse don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/).

## Install

```
gleam add valid
```

## Usage

You start with an input type and validate into an output type. These two types can be different. For example:

```gleam
type UserInput { UserInput(name: Option(String), age: Int) }

type ValidUser { ValidUser(name: String, age: Int) }
```

Then you create a validator like:

```gleam
import valid.{type ValidatorResult}

fn user_validator(user: UserInput) -> ValidatorResult(ValidUser, String) {
  valid.build2(ValidUser)
  |> valid.check(user.name, valid.is_some("Please provide a name"))
  |> valid.check(user.age, valid.int_min(13, "Must be at least 13 years old"))
}
```

And run it:

```gleam
case user_valid(input) {
  Ok(valid_user) -> ...
  Error(tuple(first_error, all_errors)) -> ...
}
```

## Error type

Errors can be your own type e.g.

```gleam
import valid.{type ValidatorResult}

type Error {
  ErrorEmptyName,
  ErrorTooYoung,
}

fn user_valid(user: UserInput) -> ValidatorResult(ValidUser, String) {
  valid.build2(ValidUser)
  |> valid.check(user.name, valid.is_some(ErrorEmptyName))
  |> valid.check(user.age, valid.int_min(13, ErrorTooYoung))
}
```

## ValidatorResult

`ValidatorResult(valid, error)` is an alias for `Result(valid, tuple(error, List(error)))`

The `Ok` branch has the valid output.

The `Error` branch has a tuple `tuple(error, List(error))`.

The first value is the first error. The second value is a list with all errors (including the first).

## Validators

See the [API Docs](https://hexdocs.pm/valid/) for the list of included validators.

## Custom property validator

A property validator has two components:

- The error to return
- A function that transforms the property if successful (`fn(input) -> Option(output)`)

Example:

```gleam
import gleam/option.{None, Option, Some}
import valid

fn bigger_than_10(num: Int) -> Option(num) {
  case num > 10 {
    True ->
      Some(num)
    False ->
      None
  }
}

let custom = valid.custom("Must be bigger than 10", bigger_than_10)

let valid = fn(form: FormInput) {
  valid.build1(ValidForm)
  |> valid.check(form.quantity, custom)
}
```

## Validating a Dictionary

Use `required_in_dict` and `optional_in_dict`

```gleam
fn user_validator(dictionary: Dict(String, String)) {
  valid.build2(ValidUser)
  |> valid.check(
    dictionary,
    valid.required_in_dict("name", "Missing name")
      |> valid.and_string_is_not_empty("Please provide a name"),
  )
  |> valid.check(
    input,
    valid.optional_in_dict("age")
      |> valid.and_optional(valid.string_is_int("Please provide a valid number")),
  )
}
```

## Examples

See [the tests](https://github.com/sporto/gleam-valid/blob/main/test/valid_test.gleam) for many examples