Current section

Files

Jump to
dataval README.md
Raw

README.md

<!--
SPDX-FileCopyrightText: 2026 Quentin BETTOUM <quentin@bettoum.fr>
SPDX-License-Identifier: EUPL-1.2
-->
# 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 Version](https://img.shields.io/hexpm/v/dataval)](https://hex.pm/packages/dataval)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://dataval.hexdocs.pm/)
```sh
gleam add dataval@1
```
## Examples
### Validating a Single Field
```gleam
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
```gleam
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
For 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 the field respect a certain format (e.g. min/max length, alphanum characters)
3. Uniqueness - check that the username doesn't exist in database
```gleam
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.](./examples)
Further documentation can be found at <https://dataval.hexdocs.pm/>.
## Development
```sh
gleam test # Run the tests
```