servant
servant copied to clipboard
Expose some information about API resolution to middlewares
Recently, I tried to write a WAI Middleware that was aware of the API my application was using. In particular, given an incoming request url, I wanted to be able to tell which endpoint was used, and which parts of the url were captured.
For instance, consider the server api below:
type Api =
"foo" :> Capture "id" Int :> Get '[JSON, PlainText] Foo
:<|> "bar" :> Capture "id" Int :> Get '[JSON, PlainText] Bar
Let us request it with the following command:
curl -X GET "http://localhost/foo/12345" -H "Accept: application/json"
If the request succeeds, we should get a Foo object, encoded as JSON. The additional data I would like to get from Servant for my middleware would be something like:
{
"effective-url": "/foo/<id::Int>",
"content-type": "application/json"
}
The "content-type"
part is easy to get from the response header, but the "url"
part doesn't seem easily available.
Effectively, what I would need is a way to perform the transformation GET "/foo/12345"
=> GET "/foo/<id::Int>"
I can think of several ways for Servant to provide this information:
- Return the "effective type" that was used for each request, to be processed by the middleware. For instance, the request above would return
type EffectiveApi = "foo" :> Capture "id" Int :> Get '[JSON] Foo
- Introduce a combinator that would have the effect of including routing information in the response headers. The API above would then be written as
type Api = WithRouting :>
"foo" :> Capture "id" Int :> Get '[JSON, PlainText] Foo
:<|> "bar" :> Capture "id" Int :> Get '[JSON, PlainText] Bar
then the response headers would include "Effective-Url": "/foo/<id::Int>"
- Introduce a new
Servant.Middleware
that would be aware of API resolution mechanisms.
You can do this to day with the servant-ekg
library's HasEndpoint
typeclass though it might be more beneficial to move HasEndpoint
to servant proper
withEndpoints :: HasEndpoint api => Proxy api -> Middleware
withEndpoints proxy req resp app = do
case getEndpoint proxy req of
Nothing -> putStrLn "unknown"
Just (APIEndpoint pathParts _method) = print pathParts
app req resp