-
-
Notifications
You must be signed in to change notification settings - Fork 414
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
Allow customizing the response code at the term level (at runtime) without sacrificing content negotiation #732
Comments
I've thought about this a decent amount, though haven't really written much code for it. Here are two main options: Simple : Accept header to error mappingMy opinion is that we should still have a single concrete type (like An obvious candidate is something like: data NewServantErr = NewServantErr
{ acceptHeaderMapping :: Map AcceptHeader OldServantErr
, defaultErr :: OldServantErr
} We'd of course provide convenience functions around that. Combinators could use this same format, so it'd be related to #353 and #685 insofar as we probably want something that works in both cases. This approach is simple, easy to implement, and isn't as large a change for end-users as the next one. The downside is that:
Ambitious : Typed errorsThe other option is to have information about the error type in the API type. So e.g. instead of: type API = Get '[JSON] SuccessResponse We'd have type API = Get '[JSON, HTML] ErrorResponse SuccessResponse Where class IsError a where
type PossibleStatusCodes :: [Nat]
statusCodeOf :: a -> StatusCode Which Alternatively, instead of adding a type to the API type, we could start expecting the type of the handler response itself to have an instance of an Even though I also don't think this one is too hard to implement, actually. Note that I've somewhere advocated adding a lot more info to the API type. This is a sort of toned-down version of that. In this approach, I'd say #353 and #685 are pretty much unrelated. |
@lexi-lambda the |
I like the ambitious approach. If we change EDIT: we'll need to be able to create |
I like the "ambitious" approach as well, it feels like it's a better direction to follow. The trickiest part I think would be to come up with the simplest API that would expose this new mechanism to users. I'm not saying we should aim for a perfect API right from the start, but rather "let's put some thoughts into it now and refine over time as we get feedback". |
Could/Should we leave this a project for ZuriHac'17? (we could create a label, and tag potential tasks)? |
That could be a nice task indeed. If someone wants to tackle this (or even just get started) before that would be very welcome by everyone though I think =) |
I also like the ambitious approach. |
I agree with all the feedback here; the “ambitious” approach looks great. I’d consider working on implementing some of it myself, but I am not sure that I know nearly enough about how servant works to help much. |
I was talking to @alpmestan about this through email and he suggested I post about it here. I've wanted something like this for a while now, and I am currently writing some code to solve a similar problem to this. Here's a work-in-progress: https://github.com/cdepillabout/servant-checked-exceptions I currently haven't written very much documentation, but you can get an idea of how it works by looking at this example: Also possibly the My main goals with this were the following:
I feel like the current incarnation of the code has solved these two problems. However, I still haven't figured out a good solution to the following problems:
|
@cdepillabout, that’s really neat. The use of extensible sum types is really smart, and it seems like they fit in pretty naturally here. The lack of short circuiting is unfortunate, but it seems like that could be implemented using a different handler monad using Do you think servant-checked-exceptions is in a place where I could give it a try? Even if it’s highly unstable, I’d still be interested in just playing with it a bit. |
@lexi-lambda I've released https://github.com/cdepillabout/servant-checked-exceptions As far as short circuiting is concerned, I created an issue about it. I'm not sure of a good way to make it work (easily) with servant, but if you have any ideas I'd definitely be interested in hearing them. |
ZuriHac note: This is advanced-expert task. See Ambitious : Typed errors in #732 (comment) |
I've updated the servant-checked-exceptions to be able to easily set the HTTP Status for errors. A simple example would look like the following. This is similar to what @lexi-lambda and @jkarni were describing:
I posted this in a previous comment, but to rehash on the benefits of using
The following problems still exist:
P.S. |
@cdepillabout Would you perhaps feel like writing a cookbook entry for your library? |
@alpmestan Yes, I would be willing to do that. I added an issue to make sure I wouldn't forget. However, I still haven't gotten the package documentation updated for this latest release, so that will have to happen first. |
No problem :) Thanks in advance! |
I was encouraged to open this issue based on a brief Twitter conversation with @alpmestan.
Currently, servant is super awesome as long as nothing goes wrong. I can write a high-level, declarative specification of what each of my API endpoints responds with, and I can keep all the rendering code separate from my logic. For example, imagine I have a simple API that looks like this:
If the client requests
/divide/6/3
in JSON, then the result might look like this:If the client requests HTML, then the result might look like this:
This is great! But what if the user is naughty, and they try to request
/divide/1/0
? Obviously, I don’t want my server to blow up, so I tweak my handler to handle this case:Now I can add a case to my
ToJSON
andToHTML
instances to produce different errors upon failure, depending on the requested content type:However, I have a problem with this: my server is still responding with 200 OK, even though the response is obviously an error. It seems like my only option here is to respond with
ServantErr
, which makes sense, given that this is an error case, and I want the short-circuiting behavior ofExceptT
. Sadly, if I produceServantErr
, I lose my nicely-formatted errors based on what content type the client requested.In practice, I usually use a custom monad transformer stack for my handlers, and I use
Nat
to convert it toHandler
. This doesn’t actually help at all, though, since I don’t have any access to the content type information at that point, so I can’t make the decision there.There are probably a few ways to handle this. One way would be to add a different combinator,
GetWithStatus
, that allows the handler to returnHandler (StatusCode, a)
instead ofHandler a
. The downside to this, of course, is that it is remarkably non-typesafe, and it means you don’t get short-circuiting behavior ofExceptT
.A better solution would probably be to have a custom error type for an entire API, rather than
ServantErr
, which would be able to take advantage of content types to control how they are presented in the response. Here’s a completely imaginary interface for something like that:However, I’m not sure how feasible that is.
This seems related to #353 and #685, though this seems somewhat different, since it’s about application-specific errors rather than errors produced by servant. It’s probably the same issue as #296, though I’m not sure if things have changed much since then.
The text was updated successfully, but these errors were encountered: