-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.

module Network.Wai.Routing.Tutorial
  ( -- * Motivation
    -- $motivation

    -- * Introduction
    -- $introduction

    -- * Example Predicate
    -- $example

    -- * Routes
    -- $routes
  )
where

{- $motivation

The purpose of the @wai-routing@ package is to facilitate the
convenient definition of safe WAI 'Application's. Here safety
means that a handler can declare all pre-conditions which must be
fulfilled such that the handler can produce a successful response (excluding
the request body). It is then statically guaranteed that the handler will not be
invoked if any of these pre-conditions fails.

-}

{- $introduction

The @wai-routing@ package defines a 'Network.Wai.Routing.Predicate.Predicate.Boolean' type
which carries \-\- in addition to actual truth values @T@ and @F@ \-\- meta-data for each case:

@
data Boolean f t
    = F f
    | T Delta t
    deriving (Eq, Show)
@

'Network.Wai.Routing.Predicate.Predicate.Delta' can in most instances be ignored, i.e. set to 0.
Its purpose is as a measure of distance for those predicates which evaluate
to @T@ but some may be \"closer\" in some way than others. An
example is for instance HTTP content-negotiations (cf.
'Network.Wai.Routing.Predicate.Accept.Accept')

In addition there is a type-class 'Network.Wai.Routing.Predicate.Predicate.Predicate' defined which
contains an evaluation function 'Network.Wai.Routing.Predicate.Predicate.apply', where the
predicate instance is applied to some value, yielding @T@ or @F@.

@
class Predicate p a where
    type FVal p
    type TVal p
    apply :: p -> a -> Boolean (FVal p) (TVal p)
@

All predicates are instances of this type-class, which does not
specify the type against which the predicate is evaluated, nor the types
of actual meta-data for the true/false case of the Boolean returned.
WAI related predicates are defined against 'Network.Wai.Routing.Request.Req'
which holds a regular 'Network.Wai.Request' and path capture variables.
In case predicates fail, they return a status code and an optional message.

Besides these type definitions, there are some ways to connect two
predicates to form a new one as the logical @OR@ or the
logical @AND@ of its parts. These are:

  * 'Network.Wai.Routing.Predicate.Predicate.:|:' and 'Network.Wai.Routing.Predicate.Predicate.:||:' as logical @OR@s

  * 'Network.Wai.Routing.Predicate.Predicate.:&:' as logical @AND@

In addition to evaluating to @T@ or @F@ depending on the truth values of
its parts, these connectives also propagate the meta-data and @Delta@
appropriately.

If @:&:@ evaluates to @F@ it has to combine the meta-data of both predicates,
and it uses the product type 'Network.Wai.Routing.Predicate.Predicate.:::' for this.
This type also has a data constructor with the same symbol, so one can
combine many predicates without having to nest the meta-data pairs.

In the @OR@ case, the two predicates have potentially meta-data of
different types, so we use a sum type 'Either' whenever we combine
two predicates with @:||:@. For convenience a type-alias
@:+:@ is defined for 'Either', which allows simple infix
notation. However, for the common case where both predicates have
meta-data of the same type, there is often no need to distinguish which
@OR@-branch was true. In this case, the @:|:@ combinator can be used.

Finally there are 'Network.Wai.Routing.Predicate.Predicate.Const' and
'Network.Wai.Routing.Predicate.Predicate.Fail' to always evaluate to @T@ or @F@
respectively.

As an example of how these operators are used, see below in section \"Routes\".
-}

{- $example

@
newtype Query = Query ByteString

instance Predicate Query Req where
    type FVal Query = Error
    type TVal Query = ByteString
    apply (Query x) r =
        case lookupQuery x r of
            []    -> F (Error 400 (Just $ \"Expected parameter '\" \<\> x \<\> \"'.\"))
            (v:_) -> T [] v
@

This is a simple example looking for the existence of a 'Req' query
parameter with the given name. In the success case, the query value is
returned.

As mentioned before, WAI predicates usually fix the type @a@ from
@Predicate@ above to 'Network.Wai.Routing.Request.Req'. The associated
types 'Network.Wai.Routing.Predicate.Predicate.FVal' and
'Network.Wai.Routing.Predicate.Predicate.TVal' denote the meta-data
types of the predicate. In this example, the meta-date type is
'Data.ByteString.ByteString'. The @F@-case is 'Network.Wai.Routing.Error.Error'
which contains a status code and an optional message.

-}

{- $routes

So how are @Predicate@s used in an application?
One way is to just evaluate them against a given request, e.g.

@
someHandler :: Application
someHandler r =
    case apply (accept :&: query \"baz\") (fromWaiRequest [] r) of
        T ((_ :: Media \"text\" \"plain\") ::: bazValue) -> ...
        F (Just (Error st msg))                      -> ...
        F Nothing                                    -> ...
@

This however requires the manual construction of a 'Network.Wai.Routing.Request.Req' and
for brevity we did not provide the list of captured path parameters.
The intended application of @wai-routing@ is to declare route definitions with the
'Network.Wai.Routing.Route.Routes' monad which can be turned into a WAI @Application@
generalised from IO to arbitrary @Monad@s through 'Network.Wai.Routing.Route.route'.
This application will at runtime select the actual handler to invoke (using the @wai-route@
library).

@
sitemap :: Routes ()
sitemap = do
    get  \"\/a\" handlerA $ accept :&: (query \"name\" :|: query \"nick\") :&: query \"foo\"
    get  \"\/b\" handlerB $ accept :&: (query \"name\" :||: query \"nick\") :&: query \"foo\"
    get  \"\/c\" handlerC $ failure (Error 410 (Just \"Gone.\"))
    post \"\/d\" handlerD $ accept
    post \"\/e\" handlerE $ accept
@

Handler definitions encode their pre-conditions in their type-signature:

@
handlerA :: Media \"application\" \"json\" ::: ByteString ::: ByteString -> IO Response
handlerB :: Media \"text\" \"plain\" ::: (ByteString :+: ByteString) ::: ByteString -> IO Response
handlerC :: Media \"application\" \"json\" ::: Char -> IO Response
handlerD :: Media \"application\" \"x-protobuf\" -> IO Response
handlerE :: Media \"application\" \"xml\" -> IO Response
@

The type-declaration of a handler has to match the corresponding predicate,
i.e. the type of the predicate's @T@ meta-data value.

One thing to note is that @Fail@ works with
all @T@ meta-data types which is safe because the handler is never
invoked, or @Fail@ is used in some logical disjunction.
-}