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]]
== Constraints == Constraints
Cowboy provides an optional constraints based validation feature Constraints are validation and conversion functions applied
when interacting with user input. to user input.
Constraints are first used during routing. The router uses They are used in various places in Cowboy, including the
constraints to more accurately match bound values, allowing router and the request match functions.
to create routes where a segment is an integer for example,
and rejecting the others.
Constraints are also used when performing a match operation === Syntax
on input data, like the query string or cookies. There, a
default value can also be provided for optional values.
Finally, constraints can be used to not only validate input, Constraints are provided as a list of fields. For each field
but also convert said input into proper Erlang terms, all in in the list, specific constraints can be applied, as well as
one step. 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 Note that when used with the router, only the second form
field a list of constraints for that field can be provided. 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 Constraints for each field are provided as an ordered list
one or more constraints; or the name, one or more constraints of atoms or funs to apply. Built-in constraints are provided
and a default value. as atoms, while custom constraints are provided as funs.
When no default value is provided then the field is required. When multiple constraints are provided, they are applied in
Otherwise the default value is used. 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 For example, the following constraints will first validate
in the order they are given. If the value is modified by a and convert the field `my_value` to an integer, and then
constraint, the next constraint receives the updated value. 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
Built-in constraints are specified as an atom:
[cols="<,<",options="header"] [cols="<,<",options="header"]
|=== |===
| Constraint | Description | Constraint | Description
| int | Convert binary value to integer. | int | Converts binary value to integer.
| nonempty | Ensures the binary value is non-empty. | nonempty | Ensures the binary value is non-empty.
|=== |===
=== Custom constraint === Custom constraints
In addition to the predefined constraints, Cowboy will accept Custom constraints are specified as a fun. This fun takes
a fun. This fun must accept one argument and return one of a single argument and must return one of `true`, `{true, NewValue}`
`true`, `{true, NewValue}` or `false`. The result indicates or `false`.
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.
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.