Valid

CI

A validation library for Gleam.

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

This library follows the principle 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:

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

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

Then you create a validator like:

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:

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

Error type

Errors can be your own type e.g.

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 for the list of included validators.

Custom property validator

A property validator has two components:

Example:

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

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 for many examples

Search Document