formal

Type safe HTML form decoding and validation!

Package Version Hex Docs

gleam add formal@3
import formal/form

// Define a type that is to be decoded from the form data
pub type SignUp {
  SignUp(email: String, password: String)
}

/// A form that decodes the `Signup` value.
fn signup_form() -> Form(Signup) {
  form.new({
    use email <- form.field("email", {
      form.parse_email
    })
    use password <- form.field("password", {
      form.parse_string
      |> form.check_string_length_more_than(7)
    })
    use _ <- form.field("confirm", {
      form.parse_string
      |> form.check_confirms(password)
    })
    form.success(SignUp(email: email, password: password))
  })
}

// This function takes the list of key-value string pairs that a HTML form
// produces. It then decodes the form data into a SignUp value, ensuring that
// all the fields are present and valid.
//
pub fn handle_form_submission(values: List(#(String, String))) {
  let result =
    signup_form()
    |> form.add_values(values)
    |> form.run

  case result {
    Ok(data) -> {
      // Do something with the SignUp value here
    }
    Error(form) -> {
      // Re-render the form with the error messages
    }
  }
}

Example applications

Parsing variants example

Sometime you may have a complex form that could be in one of several formats, and you wish to parse the different variants of this form into a single Gleam custom type. This can be done by having a hidden input on the forms to indicate which variant it is, and then pattern matching on the result.

/// This variant indicates which variant it is
pub type VisitorKind {
  GuestKind
  UserKind
}

/// A parser for that type
fn parse_visitor_kind() {
  form.parse(fn(values) {
    case values {
      ["guest", ..] -> Ok(GuestKind)
      ["user", ..] -> Ok(UserKind)
      _ -> Error(#(WibbleForm, "must be guest or user"))
    }
  })
}

/// The custom type to decode the form into
type VisitorForm {
  GuestForm(email: String)
  UserForm(username: String)
}

/// In the form function the kind is parsed first, and then depending on which
/// variant it is different form logic follows.
fn wibble_form() {
  form.new({
    use variant <- form.field("kind", parse_visitor_kind())

    case variant {
      GuestKind -> {
        use email <- form.field("email", form.parse_email)
        form.success(GuestForm(email:))
      }
      UserKind -> {
        use wobble <- form.field("username", form.parse_string)
        form.success(UserForm(username:))
      }
    }
  })
}

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

Search Document