From 8777f631caff2aef7aa4ac36f1c0370ee061388f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= <essen@ninenines.eu>
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/constraints.asciidoc')

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

