From 8777f631caff2aef7aa4ac36f1c0370ee061388f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Sun, 4 Sep 2016 19:09:51 +0200 Subject: Rework the constraints chapter --- doc/src/guide/constraints.asciidoc | 101 +++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 32 deletions(-) (limited to 'doc/src/guide') diff --git a/doc/src/guide/constraints.asciidoc b/doc/src/guide/constraints.asciidoc index 0ae01d5..822962d 100644 --- a/doc/src/guide/constraints.asciidoc +++ b/doc/src/guide/constraints.asciidoc @@ -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 + +Custom constraints are specified as a fun. This fun takes +a single argument and must return one of `true`, `{true, NewValue}` +or `false`. + +`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. +---- -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. +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. -Note that constraint functions SHOULD be pure and MUST NOT crash. +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. -- cgit v1.2.3