This commit introduces a new type-level combinator, `WithRoutingHeader`.
It modifies the behaviour of the following sub-API, such that all endpoint
of said API return an additional routing header in their response.
A routing header is a header that specifies which endpoint the
incoming request was routed to.
Endpoint are designated by their path, in which `Capture'` and
`CaptureAll` combinators are replaced by a capture hint.
This header can be used by downstream middlewares to gather
information about individual endpoints, since in most cases
a routing header uniquely identifies a single endpoint.
Example:
```haskell
type MyApi =
WithRoutingHeader :> "by-id" :> Capture "id" Int :> Get '[JSON] Foo
-- GET /by-id/1234 will return a response with the following header:
-- ("Servant-Routed-Path", "/by-id/<id::Int>")
```
To achieve this, two refactorings were necessary:
* Introduce a type `RouterEnv env` to encapsulate the `env` type
(as in `Router env a`), which contains a tuple-encoded list of url
pieces parsed from the incoming request.
This type makes it possible to pass more information throughout the
routing process, and the computation of the `Delayed env c` associated
with each request.
* Introduce a new kind of router, which only modifies the RouterEnv, and
doesn't affect the routing process otherwise.
`EnvRouter (RouterEnv env -> RouterEnv env) (Router' env a)`
This new router is used when encountering the `WithRoutingHeader`
combinator in an API, to notify the endpoints of the sub-API that they
must produce a routing header (this behaviour is disabled by default).
This commit introduces a `CaptureHint` type, which is passed as an extra
argument to the `CaptureRouter` and `CaptureAllRouter` constructors for
the `Router'` type.
`CaptureHint` values are then used in `routerLayout`, to display the
name and "type" of captured values (single or list), instead of just
"<capture>" previously.
N.B.:
Because the `choice` smart constructor for routers can aggregate
`Capture` combinators with different capture hints, the `Capture*Router`
constructors actually take a *list* of `CaptureHint`, instead of a
single one.
Currently there is no way for Servant users to customize formatting of
error messages that arise when combinators can't parse URL or request
body, apart from reimplementing those combinators for themselves or
using middlewares.
This commit adds a possibility to specify custom error formatters
through Context.
Fixes#685
In case that a sub-server doesn't support the content-type specified
in the request invoke `delayedFail` instead of `delayedFailFatal` in
order to give the chance to other sub-servers to handle the request.
We use NoContent to signify an empty response nowadays. This commit
replaces all occurences of () with NoContent so that all packages use
the new semantics.
This is a reasonably simple attempt at fixing #460.
By moving the accept check to a place before the body check,
we can make it recoverable (the body check is irreversible,
so everything done after the body check has to fail fatally).
The advantage is that we can now specify routes offering
different content types modularly. Failure to match one
is not fatal, and will result in subsequent routes being
tried.
The disadvantage is that we hereby bump the error priority
of the 406 status code. If a request contains a bad accept
header and a bad body, we now get 406 rather than 400. This
deviates from the HTTP decision diagram we try to follow,
but seems like an acceptable compromise for now.
We've previously used functions in the Router type to provide
information for subrouters. But this accesses the Requests too
early, and breaks sharing of the router structure in general,
causing the Router or large parts of the Router to be recomputed
on every request.
We now do not use functions anymore, and properly compute all
static parts of the router first, and gain access to the request
only in Delayed.
This also turns the code used within Delayed into a proper monad
now called DelayedIO, making some of the code using it a bit
nicer.
* Improves how Routers are built, in particular via
the `choice` smart constructors. Static lookups are
now used more often.
* We now have test cases making sure that certain
routers have the same structure.
* The router structure can now be visualized for debugging
purposes as a tree. The new functions `layout` and
`layoutWithContext` do this.
This introduces a `Delayed` type in `RoutingApplication.hs` that
contains a handler together with delayed checks. There are several
blocks of delayed checks, so that we can ultimately execute them in the
order we desire.
The process is documented in more detail in `RoutingApplication.hs`.