dataval/form

Compose a form from multiple fields.

A form is a function from a Dict(String, String) of raw field values to a FormState. Use form.validate to run it against a list of #(field_name, value) tuples.

Example

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))
}

pub 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")])
}

Details

Each form.field looks up the field name in the raw input, runs its validators and parser, and passes the parsed value to the continuation.

Missing fields are treated as empty strings. If any field has errors, validate returns Error with a dictionary of field names to errors.

Types

A form is a function that takes raw input values as a dictionary of field names to strings, and returns a FormState containing the built value and any validation errors.

Build forms using form.create and form.field.

pub type Form(a, custom) =
  fn(dict.Dict(String, String)) -> FormState(a, custom)

State produced while validating a form.

  • value is built incrementally
  • errors maps field names to their errors
pub opaque type FormState(a, custom)

Values

pub fn create(
  value: a,
) -> fn(dict.Dict(String, String)) -> FormState(a, custom)

Finish a form pipeline by returning a successfully built value.

This is the last step of a form. It produces a form with no errors and no remaining fields to validate.

Example

pub type CreateUser {
  CreateUser(age: Int, name: String)
}

fn create_user_form() {
  use age <- form.field(
    field.new("age")
    |> field.with_parser(parser.int)
  )
  use name <- form.field(
    field.new("name")
    |> field.add_validator(validator.str_min(2))
  )
  form.create(CreateUser(age:, name:))
}
pub fn field(
  form_field: @internal Field(a, custom),
  continuation: fn(a) -> fn(dict.Dict(String, String)) -> FormState(
    b,
    custom,
  ),
) -> fn(dict.Dict(String, String)) -> FormState(b, custom)

Attach a field to the form. Missing fields in the raw input are treated as empty strings.

pub fn validate(
  form: fn(dict.Dict(String, String)) -> FormState(a, custom),
  values: List(#(String, String)),
) -> Result(a, dict.Dict(String, List(field.FieldError(custom))))

Run the form against raw input. Returns Ok(value) only if every field is valid.

Search Document