0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-15 04:30:25 +00:00

Rework the constraints chapter

This commit is contained in:
Loïc Hoguin 2016-09-04 19:09:51 +02:00
parent 2a6359ecc1
commit 8777f631ca

View file

@ -1,54 +1,91 @@
[[constraints]]
== Constraints
Cowboy provides an optional constraints based validation feature
when interacting with user input.
Constraints are validation and conversion functions applied
to user input.
Constraints are first used during routing. The router uses
constraints to more accurately match bound values, allowing
to create routes where a segment is an integer for example,
and rejecting the others.
They are used in various places in Cowboy, including the
router and the request match functions.
Constraints are also used when performing a match operation
on input data, like the query string or cookies. There, a
default value can also be provided for optional values.
=== Syntax
Finally, constraints can be used to not only validate input,
but also convert said input into proper Erlang terms, all in
one step.
Constraints are provided as a list of fields. For each field
in the list, specific constraints can be applied, as well as
a default value if the field is missing.
=== Structure
A field can take the form of an atom `field`, a tuple with
constraints `{field, Constraints}` or a tuple with constraints
and a default value `{field, Constraints, Default}`.
The `field` form indicates the field is mandatory.
Constraints are provided as a list of fields and for each
field a list of constraints for that field can be provided.
Note that when used with the router, only the second form
makes sense, as it does not use the default and the field
is always defined.
Fields are either the name of the field; the name and
one or more constraints; or the name, one or more constraints
and a default value.
Constraints for each field are provided as an ordered list
of atoms or funs to apply. Built-in constraints are provided
as atoms, while custom constraints are provided as funs.
When no default value is provided then the field is required.
Otherwise the default value is used.
When multiple constraints are provided, they are applied in
the order given. If the value has been modified by a constraint
then the next one receives the new value.
All constraints for a field will be used to match its value
in the order they are given. If the value is modified by a
constraint, the next constraint receives the updated value.
For example, the following constraints will first validate
and convert the field `my_value` to an integer, and then
check that the integer is positive:
[source,erlang]
----
PositiveFun = fun(V) when V > 0 -> true; (_) -> false end,
{my_value, [int, PositiveFun]}.
----
When there's only one constraint, it can be provided directly
without wrapping it into a list:
[source,erlang]
----
{my_value, int}
----
=== Built-in constraints
Built-in constraints are specified as an atom:
[cols="<,<",options="header"]
|===
| Constraint | Description
| int | Convert binary value to integer.
| int | Converts binary value to integer.
| nonempty | Ensures the binary value is non-empty.
|===
=== Custom constraint
=== Custom constraints
In addition to the predefined constraints, Cowboy will accept
a fun. This fun must accept one argument and return one of
`true`, `{true, NewValue}` or `false`. The result indicates
whether the value matches the constraint, and if it does it
can optionally be modified. This allows converting the value
to a more appropriate Erlang term.
Custom constraints are specified as a fun. This fun takes
a single argument and must return one of `true`, `{true, NewValue}`
or `false`.
Note that constraint functions SHOULD be pure and MUST NOT crash.
`true` indicates the input is valid, `false` otherwise.
The `{true, NewValue}` tuple is returned when the input
is valid and the value has been converted. For example,
the following constraint will convert the binary input
to an integer:
[source,erlang]
----
fun (Value0) when is_binary(Value0) ->
try binary_to_integer(Value0) of
Value -> {true, Value}
catch _:_ ->
false
end.
----
Constraint functions should only crash because the programmer
made an error when chaining constraints incorrectly (for example
if the constraints were `[int, int]`, and not because of input.
If the input is invalid then `false` must be returned.
In our snippet, the `is_binary/1` guard will crash only
because of a programmer error, and the try block is there
to ensure that we do not crash when the input is invalid.