Dataval

Data validation in Gleam.

Handle your data validation by composing forms while still being able to validate each field individually. This allows you to validate inputs as the user fills them while reusing the same validation logic for the whole form when it's submitted.

Improve the UX of your forms by defining validation steps.

Package VersionHex Docs

gleam add dataval@1

Examples

Validating a Single Field

import dataval/field
import dataval/parser
import dataval/validator
fn age_field() {
field.new("age")
|> field.with_parser(parser.int)
|> field.add_validator(validator.int_min(18))
}
fn validate_age(age: String) -> Result(Int, List(field.FieldError(a))) {
field.validate(age_field(), age)
}
pub fn main() {
assert Ok(18) == validate_age("18")
assert Error([field.IntTooSmall(18)]) == validate_age("17")
}

Composing a Form out of Multiple Fields

import dataval/field
import dataval/form
import dataval/parser
import dataval/validator
import gleam/dict
pub type CreateUser {
CreateUser(age: Int, name: String)
}
fn age_field() {
field.new("age")
|> field.with_parser(parser.int)
|> field.add_validator(validator.int_min(18))
}
fn name_field() {
field.new("name")
|> field.add_validator(validator.str_min(2))
}
fn create_user_form() {
use age <- form.field(age_field())
use name <- form.field(name_field())
form.create(CreateUser(age:, name:))
}
pub fn main() {
let user_form = create_user_form()
assert Ok(CreateUser(20, "Toto"))
== form.validate(user_form, [#("age", "20"), #("name", "Toto")])
assert Error(
dict.from_list([
#("age", [field.IntTooSmall(18)]),
#("name", [field.StringLengthTooShort(2)]),
]),
)
== form.validate(user_form, [#("age", "16"), #("name", "a")])
}

Define Validation Steps

Fore example, for the username field of a registration form, we could define 3 steps:

  1. Required - check that the field is not empty
  2. Format - check that he field respect a certain (e.g. min/max length, alphanum characters)
  3. Uniqueness - check that the username doesn't exist in database
import dataval/field
import dataval/validator
pub type UsernameErrors {
UsernameExists
}
fn username_field() {
field.new("name")
|> field.add_validator(validator.required)
// If the field is not empty, check its length
|> field.add_step_validator(validator.str_min(3))
// If length is ok, check if the name is not taken
|> field.add_step_validator(fn(username) {
// Here you would typically make the database check
case username == "already_used" {
True -> Error([field.CustomError(UsernameExists)])
False -> Ok(username)
}
})
}
pub fn main() {
assert Ok("toto") == username_field() |> field.validate("toto")
assert Error([field.MustNotBeEmpty]) == username_field() |> field.validate("")
assert Error([field.StringLengthTooShort(3)])
== username_field() |> field.validate("a")
assert Error([field.CustomError(UsernameExists)])
== username_field() |> field.validate("already_used")
}

More examples can be found here.

Further documentation can be found at https://dataval.hexdocs.pm/.

Development

gleam test # Run the tests