Dream_html.Form
Typed, extensible HTML form decoder with error reporting for form field validation failures. Powerful chained decoding functionality–the validation of one field can depend on the values of other decoded fields.
See the bottom of the page for complete examples.
The type of a decoder for a single form field value of type 'a
which can successfully decode the field value or fail with an error message key.
In the following type decoders, the minimum and maximum values are all inclusive.
val bool : bool ty
val char : ?min:char -> ?max:char -> char ty
val float : ?min:float -> ?max:float -> float ty
val int : ?min:int -> ?max:int -> int ty
val int32 : ?min:int32 -> ?max:int32 -> int32 ty
val int64 : ?min:int64 -> ?max:int64 -> int64 ty
val string : ?min_length:int -> ?max_length:int -> string ty
val unix_tm : ?min:Unix.tm -> ?max:Unix.tm -> Unix.tm ty
This can parse strings with the formats 2024-01-01
or 2024-01-01T00:00:00
into a timestamp.
Note that this is not timezone-aware.
The type of a form (a form field by itself is also considered a form) which can decode values of type 'a
or fail with a list of error message keys.
val ok : 'a -> 'a t
ok value
is a form field that always successfully returns value
.
val error : string -> string -> 'a t
error name message
is a form field that always errors the field name
with the message
.
These allow adding adding further checks to the entire form using all decoded field values and then attaching more errors to specific fields (or not).
list ?min_length ?max_length ty name
is a form field which can decode a list of values which can each be decoded by ty
. The list must have at least min_length
and at most max_length
(inclusive).
optional ty name
is a form field which can decode an optional value from the form.
required ?default ty name
is a form field which can decode a required value from the form. If at least one value corresponding to the given name
does not appear in the form, and if a default
value is not specified, the decoding fails with an error.
ensure message condition field ty name
is a form field which imposes an additional condition
on top of the existing field
. If the condition fails, the result is an error message
. It is suggested that the message
be a translation key so that the application can be localized to different languages.
let* start_date = required unix_tm "start-date"
decodes a form field and allows accessing it in the subsequent decoders. Eg:
let* start_date = required unix_tm "start-date" in
let+ end_date = required (unix_tm ~min:start_date) "end-date" in
...
However, note that let*
uses a 'fail-fast' decoding strategy. If there is a decoding error, it immediately returns the error without decoding the subsequent fields. (Which makes sense if you think about the example above.) So, in order to ensure complete error reporting for all fields, you would need to use let+
and and+
.
let+ email = required string "email"
decodes a form field named email
as a string
.
and+ password = required string "password"
continues decoding in an existing form declaration and decodes a form field password
as a string
.
val validate :
'a t ->
(string * string) list ->
('a, (string * string) list) Stdlib.result
validate form values
is a result of validating the given form
's values
. It may be either some value of type 'a
or a list of form field names and the corresponding error message keys.
pp_error
is a helper pretty-printer for debugging/troubleshooting form validation errors.
When errors are reported, the following keys are used instead of English strings. These keys can be used for localizing the error messages. The suggested default English translations are given below.
These keys are modelled after Play Framework.
Basic complete example:
type user = { name : string; age : int option }
open Dream_html.Form
let user_form =
let+ name = required string "name"
and+ age = optional (int ~min:16) "age" in
(* Thanks, Australia! *)
{ name; age }
let dream_form = ["age", "42"; "name", "Bob"]
let user_result = validate user_form dream_form
Result: Ok { name = "Bob"; age = Some 42 }
Sad path:
validate user_form ["age", "none"]
Result: Error [("age", "error.expected.int"); ("name", "error.required")]
Decode list of values from form:
type plan = { id : string; features : string list }
let plan_form =
let+ id = required string "id"
and+ features = list string "features" in
{ id; features }
validate plan_form ["id", "foo"]
Result: Ok {id = "foo"; features = []}
validate plan_form ["id", "foo"; "features", "f1"; "features", "f2"]
Result: Ok {id = "foo"; features = ["f1"; "f2"]}
Note that the names can be anything, eg "features[]"
if you prefer.
Add further requirements to field values:
let plan_form =
let+ id = ensure "error.expected.nonempty" (( <> ) "") required string "id"
and+ features = list string "features" in
{ id; features }
validate plan_form ["id", ""]
Result: Error [("id", "error.expected.nonempty")]
Complex validation rules with multiple fields:
type req = {
id : string;
years : int option;
months : int option;
weeks : int option;
days : int option;
}
let req_form =
let+ id = required string "id" (* Both id... *)
and+ days, weeks, months, years = (* ...and period are required *)
let* days = optional int "days" in
let* weeks = optional int "weeks" in
let* months = optional int "months" in
let* years = optional int "years" in
match days, weeks, months, years with
| None, None, None, None -> error "years" "Please enter a period"
(* Only one period component is required *)
| _ -> ok (days, weeks, months, years)
in
{ id; days; weeks; months; years }
validate req []
Result: Error [("years", "Please enter a period"); ("id", "error.required")]