servant icon indicating copy to clipboard operation
servant copied to clipboard

Expose some information about API resolution to middlewares

Open nbacquey opened this issue 2 years ago • 1 comments

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.

nbacquey avatar Mar 07 '22 12:03 nbacquey

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

arianvp avatar Mar 10 '22 09:03 arianvp