formal/form
Types
pub type FieldError {
MustBePresent
MustBeInt
MustBeFloat
MustBeEmail
MustBePhoneNumber
MustBeUrl
MustBeDate
MustBeTime
MustBeDateTime
MustBeColour
MustBeStringLengthMoreThan(limit: Int)
MustBeStringLengthLessThan(limit: Int)
MustBeIntMoreThan(limit: Int)
MustBeIntLessThan(limit: Int)
MustBeFloatMoreThan(limit: Float)
MustBeFloatLessThan(limit: Float)
MustBeAccepted
MustConfirm
MustBeUnique
CustomError(message: String)
}
Constructors
-
MustBePresent
-
MustBeInt
-
MustBeFloat
-
MustBeEmail
-
MustBePhoneNumber
-
MustBeUrl
-
MustBeDate
-
MustBeTime
-
MustBeDateTime
-
MustBeColour
-
MustBeStringLengthMoreThan(limit: Int)
-
MustBeStringLengthLessThan(limit: Int)
-
MustBeIntMoreThan(limit: Int)
-
MustBeIntLessThan(limit: Int)
-
MustBeFloatMoreThan(limit: Float)
-
MustBeFloatLessThan(limit: Float)
-
MustBeAccepted
-
MustConfirm
For confirmation of passwords, etc. Must match the first field.
-
MustBeUnique
For values that must be unique. e.g. user email addresses.
-
CustomError(message: String)
A form! Created from a Schema
with the new
function.
Supply values to a form with the add_*
functions and then pass it to the
run
function to get either the resulting value or any errors.
Use the language
function to supply a new translation function to change
the language of the error messages returned by the field_error_text
function.
The default language is en_gb
English.
pub opaque type Form(model)
A parser extracts a value from from values, converting it to a desired type
and optionally validating the value. Parsers are used with the field
function.
See the parse_*
and check_*
functions for more information.
Functions that start with parse_*
are short-circuiting, so any parser
functions that come afterwrads will not be run. For example, given this
code:
form.parse_int |> form.check_int_more_than(0)
If the input is not an int then parse_int
will fail, causing
check_int_more_than
not to run, so the errors will be [MustBeInt]
.
pub opaque type Parser(value)
Values
pub fn add_error(
form: Form(model),
name: String,
error: FieldError,
) -> Form(model)
Add an error to one of the fields of the form.
This function may be useful if you have some additional validation that runs outside of the form schema and you want to surface the error messages to the user via the form.
pub fn add_int(
form: Form(model),
field: String,
value: Int,
) -> Form(model)
Add an int value to the form.
You may want to use this to pre-fill a form with some already-saved values for the user to edit.
pub fn add_string(
form: Form(model),
field: String,
value: String,
) -> Form(model)
Add a string value to the form.
You may want to use this to pre-fill a form with some already-saved values for the user to edit.
pub fn add_values(
form: Form(a),
values: List(#(String, String)),
) -> Form(a)
Add multiple values to a form. This function is useful for adding values from a HTTP request form body sent to your server, or from a HTML form element in your browser-based application.
Example
use formdata <- wisp.require_form(request)
let form <- new_user_form() |> form.add_values(formdata.values)
pub fn all_errors(
form: Form(model),
) -> List(#(String, List(FieldError)))
Get all the errors within a form.
If run
or add_error
have not been called then there will be no errors.
pub fn all_values(form: Form(model)) -> List(#(String, String))
Get all the values within a form.
pub fn check(
parser: Parser(b),
checker: fn(b) -> Result(b, String),
) -> Parser(b)
Add a custom check to the parser.
Examples
let must_not_be_rude = fn(text) {
case contains_swear_word(text) {
True -> Error("must not be rude")
False -> Ok(text)
}
}
let schema = {
use name <- form.field("name", {
form.parse_string
|> form.check(must_not_be_rude)
})
form.success(Profile(name:))
}
Internationalisation
If your application supports multiple languages you will need to ensure the error string returned by your checker function is already in the desired language.
pub fn check_accepted(parser: Parser(Bool)) -> Parser(Bool)
Ensure that a bool is True
.
Example
let schema = {
use discount <- form.field("terms-and-conditions", {
form.parse_checkbox
|> form.check_accepted
})
form.success(Product(discount:))
}
pub fn check_confirms(parser: Parser(t), other: t) -> Parser(t)
Ensure that a field equals some other value. Useful for password confirmation.
Example
let schema = {
use password <- form.field("password-confirmation", {
form.parse_string
|> form.check_string_length_more_than(8)
})
use _ <- form.field("password-confirmation", {
form.parse_string
|> form.check_confirms(password)
})
form.success(User(password:))
}
pub fn check_float_less_than(
parser: Parser(Float),
limit: Float,
) -> Parser(Float)
Ensure that a float is less than a specified limit.
Example
let schema = {
use discount <- form.field("discount", {
form.parse_float
|> form.check_float_less_than(100.0)
})
form.success(Product(discount:))
}
pub fn check_float_more_than(
parser: Parser(Float),
limit: Float,
) -> Parser(Float)
Ensure that a float is more than a specified limit.
Example
let schema = {
use price <- form.field("price", {
form.parse_float
|> form.check_float_more_than(0.0)
})
form.success(Product(price:))
}
pub fn check_int_less_than(
parser: Parser(Int),
limit: Int,
) -> Parser(Int)
Ensure that an int is less than a specified limit.
Example
let schema = {
use age <- form.field("age", {
form.parse_int
|> form.check_int_less_than(150)
})
form.success(Person(age:))
}
pub fn check_int_more_than(
parser: Parser(Int),
limit: Int,
) -> Parser(Int)
Ensure that an int is more than a specified limit.
Example
let schema = {
use age <- form.field("age", {
form.parse_int
|> form.check_int_more_than(0)
})
form.success(Person(age:))
}
pub fn check_not_empty(parser: Parser(String)) -> Parser(String)
Ensure that the string value is not an empty string.
Example
let schema = {
use tags <- form.field("tag", {
form.parse_string
|> form.check_not_empty
})
form.success(Article(tags:))
}
pub fn check_string_length_less_than(
parser: Parser(String),
limit: Int,
) -> Parser(String)
Ensure that a string is less than a specified length.
Example
let schema = {
use username <- form.field("username", {
form.parse_string
|> form.check_string_length_less_than(20)
})
form.success(User(username:))
}
pub fn check_string_length_more_than(
parser: Parser(String),
limit: Int,
) -> Parser(String)
Ensure that a string is more than a specified length.
Example
let schema = {
use password <- form.field("password", {
form.parse_string
|> form.check_string_length_more_than(8)
})
form.success(User(password:))
}
pub fn en_gb(error: FieldError) -> String
Translates FieldError
s into strings suitable for showing to the user.
Examples
assert en_us(MustBeColour) == "must be a hex colour code"
pub fn en_us(error: FieldError) -> String
Translates FieldError
s into strings suitable for showing to the user.
The same as en_gb
, but with Americanised spelling of the word “color”.
Examples
assert en_us(MustBeColour) == "must be a hex color code"
pub fn field(
name: String,
parser: Parser(value),
continuation: fn(value) -> Schema(model),
) -> Schema(model)
Add a new parser to the form for a given form field name.
pub fn field_error_messages(
form: Form(model),
name: String,
) -> List(String)
Get the error messages for a field, if there are any.
The text is formatted using the translater function given with the
langauge
function. The default translater is en_gb
.
pub fn field_errors(
form: Form(model),
name: String,
) -> List(FieldError)
Get all the form.
If the run
function or the add_error
function have not been called then
the form is clean and won’t have any errors yet.
pub fn field_value(form: Form(model), name: String) -> String
Get the first values for a given form field.
Examples
let form = form |> form.add_int("one", 100)
assert form.field_value(form, "one") == "100"
assert form.field_value(form, "two") == ""
pub fn field_values(
form: Form(model),
name: String,
) -> List(String)
Get all the values for a given form field.
Examples
let form = form
|> form.add_int("one", 100)
|> form.add_string("one", "Hello")
|> form.add_string("two", "Hi!")
assert form.field_values(form, "one") == ["Hello", "100"]
pub fn language(
form: Form(model),
translator: fn(FieldError) -> String,
) -> Form(model)
Supply a transation function that will be used when converting any
FieldError
s to text that can be presented to the user.
Build-in languages:
en_gb
en_us
These functions are named using the IETF language tag for the langauge they translate to.
If no language is supplied then en_gb
is used by default.
pub fn map(
parser: Parser(t1),
mapper: fn(t1) -> t2,
) -> Parser(t2)
Convert the parsed value into another, similar to list.map
or
result.map
.
pub fn new(schema: Schema(model)) -> Form(model)
Create a new form from a schema.
Add values to the form with the add_*
functions and use run
to get
either the final value or some errors from the form and values.
Example
let schema = {
use name <- form.field("name", {
form.parse_string
|> form.check_not_empty
})
use name <- form.field("age", form.parse_int)
form.success(Person(name:, age:))
}
let form = form.new(schema)
pub fn parse(
parser: fn(List(String)) -> Result(t, #(t, String)),
) -> Parser(t)
Create a custom parser for any type.
If the parser function fails it must return two values:
- A default “zero” value of the expected type. This will be used to run the remaining parser code, and then will be discarded.
- An error message string to present to the user.
Examples
form.parse(fn(input) {
case input {
["Squirtle", ..] -> Ok(Squirtle)
["Bulbasaur", ..] -> Ok(Bulbasaur)
["Charmander", ..] -> Ok(Charmander)
_ -> Error(#(Squirtle, "must be a starter Pokémon"))
}
})
Internationalisation
If your application supports multiple languages you will need to ensure the error string returned by your parser function is already in the desired language.
pub const parse_checkbox: Parser(Bool)
Parse a bool value from a checkbox type input.
No value False
, while any value (including empty string) counts as
True
. A checked checkbox input with no explicitly set value has the value
"on"
, which is True. Unchecked checkbox inputs send no value when the
form is submitted, they are absent from the sent payload.
Example
let schema = {
use agreed <- form.field("terms-and-conditions", {
form.parse_checkbox
|> form.check_accepted
})
form.success(Signup(agreed:))
}
pub const parse_colour: Parser(String)
A parser for colour values.
Parses color strings in HTML hex format (e.g., “#FF0000”, “#00ff00”) and returns the hex color string.
Returns a MustBeColour
error if the input is not a valid hex color.
Example
let schema = {
use background_color <- form.field("background_color", form.parse_colour)
form.success(Theme(background_color:))
}
pub const parse_date: Parser(calendar.Date)
A parser for calendar dates.
Parses dates in YYYY-MM-DD format and returns a calendar.Date
value.
Returns a MustBeDate
error if the input cannot be parsed as a valid date.
Example
let schema = {
use birth_date <- form.field("birth_date", form.parse_date)
form.success(Person(birth_date:))
}
pub const parse_date_time: Parser(
#(calendar.Date, calendar.TimeOfDay),
)
A parser for datetime values.
Parses datetime strings in HTML datetime-local format (e.g.
“2023-12-25T14:30” or “2023-12-25T14:30:00”)
and returns a tuple of (calendar.Date, calendar.TimeOfDay)
.
Returns a MustBeDateTime
error if the input cannot be parsed as a valid
datetime.
Example
let schema = {
use created_at <- form.field("created_at", form.parse_date_time)
form.success(Event(created_at:))
}
pub const parse_email: Parser(String)
A parser that validates email addresses.
Performs basic email validation by checking for the presence of an “@” symbol.
Returns a MustBeEmail
error if the input is not a valid email format.
Example
let schema = {
use email <- form.field("email", form.parse_email)
form.success(Person(email:))
}
pub const parse_float: Parser(Float)
A parser for floating point numbers.
Returns a MustBeFloat
error if the input cannot be parsed as a float.
Example
let schema = {
use price <- form.field("price", {
form.parse_float
|> form.check_float_more_than(0.0)
})
form.success(Product(price:))
}
pub const parse_int: Parser(Int)
A parser for a whole number.
Returns a MustBeInt
error if the input cannot be parsed as a valid int.
Example
let schema = {
use age <- form.field("age", {
form.parse_int
|> form.check_int_more_than(0)
})
form.success(Person(age:))
}
pub fn parse_list(parser: Parser(output)) -> Parser(List(output))
A parser that applies another parser to each input value in a list.
Takes a parser for a single value and returns a parser that can handle multiple values of the same type. This is useful for form fields that can have multiple values, such as checkboxes, multi-selects, or just repeated inputs of other types.
Example
let schema = {
use tags <- form.field("tag", form.parse_list(form.parse_string))
form.success(Article(tags:))
}
This would parse multiple “tag” fields into a list of strings.
pub fn parse_optional(
parser: Parser(output),
) -> Parser(option.Option(output))
A parser that applies another parser if there is a non-empty-string input value for the field.
Example
let schema = {
use age <- form.field("tag", form.parse_optional(form.parse_int))
form.success(Person(age:))
}
This would parse an int if the form field has text in it, returning None
otherwise.
pub const parse_phone_number: Parser(String)
A parser for phone numbers.
Phone numbers are checked with these rules:
- Must be between 7 and 15 characters after removing formatting
- Must contain only digits after removing formatting characters
+
may optionally be the first character.-
,(
, and)
are permitted, but are removed.
Returns a MustBePhoneNumber
error if the input doesn’t satisfy these
rules.
Example
let schema = {
use phone <- form.field("phone", form.parse_phone_number)
form.success(Person(phone:))
}
pub const parse_string: Parser(String)
Parse a string value. This parser can never fail!
Example
let schema = {
use name <- form.field("name", {
form.parse_string
|> form.check_not_empty
})
form.success(Person(name:))
}
pub const parse_time: Parser(calendar.TimeOfDay)
A parser for time values.
Parses times in HH:MM:SS or HH:MM format and returns a calendar.TimeOfDay
value.
Returns a MustBeTime
error if the input cannot be parsed as a valid time.
Example
let schema = {
use start_time <- form.field("start_time", form.parse_time)
form.success(Event(start_time:))
}
pub const parse_url: Parser(uri.Uri)
A parser for URLs.
Uses the gleam/uri
module to parse and validate URLs. Returns a
MustBeUrl
error if the input cannot be parsed as a valid URI.
Example
let schema = {
use website <- form.field("website", form.parse_url)
form.success(Company(website:))
}
pub fn run(form: Form(model)) -> Result(model, Form(model))
Run a form, returning either the successfully parsed value if there are no errors, or a new instance of the form with the errors added to the fields.
pub fn set_values(
form: Form(a),
values: List(#(String, String)),
) -> Form(a)
Replace any existing values of a form with new values. This function is useful for adding values from a HTTP request form body sent to your server, or from a HTML form element in your browser-based application.
Example
use formdata <- wisp.require_form(request)
let form <- new_user_form() |> form.set_values(formdata.values)