dataval/field
Core building blocks for composing a single field validation.
Example
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) {
field.validate(age_field(), age)
}
pub fn main() {
assert Ok(18) == validate_age("18")
assert Error([field.IntTooSmall(18)]) == validate_age("17")
}
Types
pub type Field(a, custom) {
Field(name: String, run: fn(String) -> FieldState(a, custom))
}
Constructors
-
Field(name: String, run: fn(String) -> FieldState(a, custom))
pub type FieldError(custom) {
MustNotBeEmpty
MustBeInt
MustBeFloat
MustBeBool
StringLengthTooShort(min: Int)
StringLengthTooLong(max: Int)
IntTooSmall(min: Int)
IntTooLarge(max: Int)
FloatTooSmall(min: Float)
FloatTooLarge(max: Float)
MustMatchRegexp
CustomError(custom)
}
Constructors
-
MustNotBeEmpty -
MustBeInt -
MustBeFloat -
MustBeBool -
StringLengthTooShort(min: Int) -
StringLengthTooLong(max: Int) -
IntTooSmall(min: Int) -
IntTooLarge(max: Int) -
FloatTooSmall(min: Float) -
FloatTooLarge(max: Float) -
MustMatchRegexp -
CustomError(custom)
pub type FieldState(a, custom) {
FieldState(
value: a,
parser_failed: Bool,
errors: List(FieldError(custom)),
)
}
Constructors
-
FieldState( value: a, parser_failed: Bool, errors: List(FieldError(custom)), )
pub type Parsed(a, custom) {
Parsed(value: a, errors: List(FieldError(custom)))
}
Constructors
-
Parsed(value: a, errors: List(FieldError(custom)))
pub type Validator(a, custom) =
fn(a) -> Result(a, List(FieldError(custom)))
Values
pub fn add_step_validator(
field: Field(a, custom),
validator: fn(a) -> Result(a, List(FieldError(custom))),
) -> Field(a, custom)
Add a validator that only runs if no previous validator or parser errored.
Use this to compose validation in multiple steps and for expensive checks like database lookups, external API calls, etc.
You can add multiple validators at once using add_step_validators.
Example
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")
}
pub fn add_step_validators(
field: Field(a, custom),
validators: List(fn(a) -> Result(a, List(FieldError(custom)))),
) -> Field(a, custom)
Add several validators inside a step. They will only run if no previous validator or parser errored.
Use this to compose validation in multiple steps and for expensive checks like database lookups, external API calls, etc.
Example
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_validators([
validator.str_min(3),
validator.str_max(20),
])
// 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")
}
pub fn add_validator(
field: Field(a, custom),
validator: fn(a) -> Result(a, List(FieldError(custom))),
) -> Field(a, custom)
pub fn add_validators(
field: Field(a, custom),
validators: List(fn(a) -> Result(a, List(FieldError(custom)))),
) -> Field(a, custom)
Add multiple validators at once.
pub fn new(name: String) -> Field(String, custom)
A field starts as a raw string and is built as a pipeline:
pub fn optional(
base: Field(a, custom),
) -> Field(option.Option(a), custom)
Make a field optional by wrapping its successful value in option.Some.
An empty string becomes option.None and skips all parsers and validators.
This must be the last step of a pipeline because it changes the field’s
type from Field(a, custom) to Field(Option(a), custom).
Example
new("name")
|> add_validator(validator.str_min(3))
|> add_validator(validator.str_max(10))
|> optional()
pub fn validate(
field: Field(a, custom),
value: String,
) -> Result(a, List(FieldError(custom)))