-
-
Notifications
You must be signed in to change notification settings - Fork 412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Introduce Redirect
combinator
#1575
base: master
Are you sure you want to change the base?
Conversation
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).
Prior art/discussions on this: #117 I'm not entirely a fan because it's pretty common to have to "inject" a dynamic value in the URL. Think of a POST endpoint to create a new something than then redirects you to If |
I tend to agree that a static redirect location is a very limited use-case that likely doesn't match the needs of most users. I wonder if we could craft something based on data Redirect (statusCode :: Nat) (api :: *) with something like: class HasServer (Redirect statusCode api) ctx where
type ServerT m (Redirect statusCode api) = m (EndpointOf api)
route = … -- Run handler and pattern match on `EndpointOf` to retrieve all the evidence to call `safeLink`.
data EndpointOf api where
EndpointOf :: (IsElem endpoint api, HasLink endpoint) => Proxy endpoint -> EndpointOf api If I am not mistaken, this would give users more flexibility while also ensuring that the link belongs to a specific API. Do you think this is doable / useful ? Of course, this doesn't solve all the problems, such as required / forbidden method changes. |
The proposal above is actually bogus, because it does not give us any way to bundle capture arguments, etc. I still wonder if something along those lines could work. EDIT: data EndpointOf api where
EndpointOf
:: (IsElem endpoint api, HasLink endpoint)
=> Proxy endpoint -> (forall a. MkLink endpoint a -> a) -> EndpointOf api |
@gdeest This was brought up in the ticket I linked to above IIRC. |
@alpmestan Not exactly in this form, as far as I understand — the idea I was playing with would allow the |
Indeed, not in this form, but this then prevents All in all, I'd find it perhaps desirable that we have a fairly flexible building block on top of which we could have something that crafts carefully statically-checked redirects to endpoints from the same API, or something that just sends people to some "random" URL. From that perspective, having |
It is (unfortunately) true. It wouldn't that much of a problem if we could just wrap the API in a Adding a type synonym is easy enough, but I fail to see how it constitutes a satisfying building block to adding more elaborate static checks. |
Functions for constructing |
This PR introduces a new combinator, to ease the implementation of correct HTTP redirections:
Current implementation
It modifies the behaviour of the nested sub-APIs, such that all endpoints of said API return a
"Location"
header, set to the value of thelocation
type variable. An API using theRedirect
combinator does not typecheck if any of the endpoints after the combinator returns a status code outside the3xx
range, or if it is used to redirect aRaw
API (because we cannot guarantee anything about those).For instance, the following API doesn't have a
HasServer
instance:Whereas this one does:
Alternative implementations
Have a
Verb
-like combinatorIn this alternative,
Redirect
would replaceVerb
,UVerb
, andStream
, and have a syntax like:It would only allow
3xx
status codes, and would behave the same asVerb
does, only forcing aLocation
header in the response.The API example above would then be re-written as:
Pros:
Cons:
Verb
would have to be duplicated forRedirect
.UVerb
-like orStream
-like way to return a body, thus creating the need forRedirectVerb
,RedirectUVerb
, andRedirectStream
, with more boilerplate and instances.Specify a status code with the
Redirect
combinatorIn this case, the combinator would have a syntax like:
It would still be used "in the middle" of an API, and would only accept
3xx
status codes. The status code would then overwrite the status code of individual endpoints. For instance, on the following API:A call to
GET /old-api
would return a301
status code instead of a200
, with a header{"Location": "/new-api"}
.Pros:
Cons:
Verb
.I would appreciate your input on the relative merits of those alternative implementations.
This PR is based on #1561, it uses the enriched
RouterEnv env
, and theEnvRouter
Constructor.Relates to #1550