-
-
Notifications
You must be signed in to change notification settings - Fork 413
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
Re-engineer Verb #841
Comments
A little note: Oleg and I have been discussing this over the past couple of days. We want to unify everything to avoid as much duplication as possible. The alternative to being able to "augment" the response information as done here would be to have all the possible "options" be type parameters to So we really want @phadej's idea to work :) This would make many adhoc things non adhoc (stream would not have to be a totally separate hierarchy of response combinators -- cc @gbaz -- while responses that provide some headers would just augment the response with those instead of having the ugly |
As a user of Servant and a drive-by commentator, I really like this proposal. (I'd probably bike shed the |
We want to hear from our users, maybe even more in this issue than usual. Feel free to suggest another operator and to illustrate with snippets of code you'd like to be able to write. |
Thank you, @alpmestan. I should have probably kept my mouth shut until I could suggest something better. I think the reason I objected initially to I also want to be clear that I think of this as bike-shedding: the overall improvement discussed here is excellent, in my opinion, so don't let me distract from that. |
Well your point about your brain being disturbed by |
One question: Would it be possible to do something like I would probably never do this, just wondering. |
Being able to express different "response paths" is one of the important goals of this issue. response path == status code + headers + content types + type of the response body, here. Ideally we want full flexibility, so as to assume nothing about what users want to do. Because there's always someone who will want to do that very thing that you thought was irrelevant =) It makes sense to only return errors in JSON while making the "success" response available in JSON and HTML, in some apps, so it's not all that far fetched after all. |
Well, in that case then, would it possible to have different code paths depending on, for instance, objects that get submitted? I imagine something like this:
In other words, is it more like an |
It's the sum-type equivalent of HList that would model things accurately here, indeed. We want to model the overall response of the endpoint as being one of several possibilities. It would be up to the endpoint's code, using some dedicated function(s), to return this or that kind of response depending on its inputs and anything else you want. But at least all the possible responses (including error responses) would be declared and all of this would be documented by servant-docs, clients would with your example give something like |
the for example, with the type alias that implies that each value can have zero or more errors,
a few other record libraries have n-ary variants, like the sum-of-products package, I mention (sorry if this isn't relevant, I only skimmed the discussion) |
I have a little bit of prior work of this in my bachelor project https://github.com/arianvp/servis/raw/master/paper/paper.pdf See the chapter about Explicit status Codes |
Brainstormed about this with @rightfold yesterday. How about something like this?: Lets ditch the idea of having a coproduct of return types You have type family IsElem x xs :: Constraint where
IsElem x '[] = TypeError (Text "No")
IsElem x (x ': xs) = ()
IsElem x (y ': xs) = IsElem x xs Then the way for people implementing a server to set a status code would be something like: setStatusCode :: IsElem x xs => Proxy x -> Server (Verb Get xs) () This disallows a server implementing the spec from setting a status code that is not in the spec. You could go one step further and use something like |
I like the direction that suggestion is going in, but (unless I'm misreading) it loses the option to specify different return types and different encodings for each return path. To put those back in would look like: Get '[(200, JSON, NormalType), (403, HTML, AuthError), etc.] Edit: Now that, I think about it, the original suggestion also had more of a combinator feel, so I could presumably do something like this: type MyAppAuthErrors = AuthRequired401 :< AuthFailure403
Verb (MyAppAuthErrors :< Full ZonedTime) |
While we're at it, we can get rid of |
Yes, we want to offer that possibility. Not sure we want to get rid of ServantErr just yet. If we solve this, we can keep ServantErr around a little bit to allow people to smoothly move to the new approach. But we're not there yet anyway :) |
Content-types should be the same for all paths, since we have to resolve
content type *before* running the code.
(We've discussed this idea a few times in the past. I think there's already
an issue for it that we should use, though I'm on my phone right now so
will only check later)
…On Saturday, November 18, 2017, Arian van Putten ***@***.***> wrote:
While we're at it, we can get rid of ServantErr right? Usually, you'd do
something like err403 to give a different error code, but now that we can
put the possible status codes in the type, a "Different status code" is not
really an error scenario anymore, just a different code path. This would
basically return our base type back to IO which is kind of neat
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#841 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABlKmkX2XeGIbHfZJJNieXhNYP40PqI2ks5s3wBHgaJpZM4QCUq6>
.
|
I like the list of tuples idea. You can verify that the combination of status code and response type exists using a constraint. respond :: IsResponse api s r => Proxy s -> r -> Server api
respond (Proxy :: Proxy '404) (CustomerNotFound uuid) |
Note this is all very much the same idea, all throughout the thread, nothing new AFAICT. But the big challenge is to make it work @phadej -style (see first post) instead of exhaustive-style (see the gist that I linked to very early in this thread). |
Changes Header, ReqBody and QueryParam to take a modifier list. First step to implement haskell-servant#856 Only adjust Links implementation. ResponseHeader story turns to be somewhat ugly, but it can be made elegant when haskell-servant#841 is implemnted, then we can omit HList aka Header Heterogenous List implementation.
Changes Header, ReqBody and QueryParam to take a modifier list. First step to implement haskell-servant#856 Only adjust Links implementation. ResponseHeader story turns to be somewhat ugly, but it can be made elegant when haskell-servant#841 is implemnted, then we can omit HList aka Header Heterogenous List implementation.
Changes Header, ReqBody and QueryParam to take a modifier list. First step to implement haskell-servant#856 Only adjust Links implementation. ResponseHeader story turns to be somewhat ugly, but it can be made elegant when haskell-servant#841 is implemnted, then we can omit HList aka Header Heterogenous List implementation.
Changes Header, ReqBody and QueryParam to take a modifier list. Resolves haskell-servant#856 ResponseHeader story turns to be somewhat ugly, but it can be made elegant when haskell-servant#841 is implemnted, then we can omit HList aka Header Heterogenous List implementation. - servant-server changes: Writing server side intepretations is quite simple using `unfoldRequestArgument`, which makes Header and QueryParam look quite the same. `ReqBody` cannot be easily made optional with current design (what that would mean: No Content-Type Header?), so that dimensions isn't used there. - Add HasLink for all the rest ComprehensiveAPI combinators - Add 'tricky' Header', QueryParam' endpoints to ComprehensiveAPI - servant-docs: Quick'n'dirty implementation. Don't use modifiers information (yet).
@alpmestan pointed me to this issue in cdepillabout/servant-checked-exceptions#4 (comment). It would be really nice if servant provided some way to encode what errors are returned by an API in a flexible manner. I'm using https://github.com/cdepillabout/servant-checked-exceptions to do it in a couple projects at work (and it is working pretty well), but it would be nice if Servant provided some sort of built-in support for this. I think this proposal of re-enginnering |
Yes, that's one of the goals. The big general goal is, now that we have a good idea of what people need from The first candidate approach is one where we would just add extra annotations to specify that we add response headers, want to stream the response while executing the handler, declare strongly typed error cases (what your package does), etc. See @phadej's post at the very beginning of this thread. The second one consists in making |
I'm pretty interested in seeing explicit errors land in servant. Seems like these type of issues are being closed as dupes of this issue, so I'm just commenting here. Here's a gist showing a way to add these to servant using a It's not a change to Biggest downside I can see is Thoughts? |
I think @cdepillabout has a package for this already ( Do let us know if @cdepillabout's package doesn't quite fit what you need, in which case it is I suppose relevant to this issue. :-) |
@benweitzman As alpmestan said, https://github.com/cdepillabout/servant-checked-exceptions Feel free to open an issue if you have any problems/suggestions. |
... and another one, in a very early proof-of-concept stage: https://github.com/wireapp/servant-uverb servant-checked-exceptions seems more mature, but it's not flexible enough for what we need, so we explored the design space a little further. |
Ha, very cool! I'll take a close look later today. |
Changes Header, ReqBody and QueryParam to take a modifier list. Resolves haskell-servant/servant#856 ResponseHeader story turns to be somewhat ugly, but it can be made elegant when haskell-servant/servant#841 is implemnted, then we can omit HList aka Header Heterogenous List implementation. - servant-server changes: Writing server side intepretations is quite simple using `unfoldRequestArgument`, which makes Header and QueryParam look quite the same. `ReqBody` cannot be easily made optional with current design (what that would mean: No Content-Type Header?), so that dimensions isn't used there. - Add HasLink for all the rest ComprehensiveAPI combinators - Add 'tricky' Header', QueryParam' endpoints to ComprehensiveAPI - servant-docs: Quick'n'dirty implementation. Don't use modifiers information (yet).
2258: ADP-291: Document endpoints error codes in the API documentation r=hasufell a=hasufell # Issue Number ADP-291 # Implementation approach This is all best-effort. There are no static checks to ensure we didn't miss anything. There's ongoing work at servant to make this possible: haskell-servant/servant#841 But even that would require a major refactor. Testing all possible errors is also an option, but seems quite excessive. The workflow for figuring out the error codes is roughly: 1. follow the entry point to the the `Handler ()` function e.g. ```hs listTransactions :: forall ctx s t k n. (ctx ~ ApiLayer s t k) => ctx ... -> Handler [ApiTransaction n] ``` 2. check all `liftHandler` calls, which have functions with `ExceptT` as argument, e.g.: ```hs listTransactions :: forall ctx s k t. ( HasDBLayer s k ctx , HasNetworkLayer t ctx ) => ctx ... -> ExceptT ErrListTransactions IO [TransactionInfo] ``` 3. find all the LiftHandler instance, e.g. ```hs instance LiftHandler ErrListTransactions where handler = \case ErrListTransactionsNoSuchWallet e -> handler e ErrListTransactionsStartTimeLaterThanEndTime e -> handler e ErrListTransactionsMinWithdrawalWrong -> apiError err400 MinWithdrawalWrong "The minimum withdrawal value must be at least 1 Lovelace." ErrListTransactionsPastHorizonException e -> handler e ``` 4. follow the recursive handlers to resolve all errors and add client error types to `swagger.yaml`. The error code is the 3rd `ApiErrorCode` argument to `apiError`. 5. Some errors are implicit, e.g. failed parameter parsing etc. These are also in `ApiErrorCode`. Also see the special instance `instance LiftHandler (Request, ServerError)` 6. repeat until exhaustion # Overview - [x] Wallets - [x] Addresses - [x] Coin Selections - [x] Transactions - [x] Migrations - [x] Stake Pools - [x] Utils - [x] Network - [x] Proxy - [x] Settings - [ ] Byron-specific endpoints # Remarks 1. I did not double check the byron endpoints. Many of their endpoints share the same types in `swagger.yaml`, so I assumed they have the same behavior wrt error codes. 2. This will generally be hard to maintain, since yaml anchors aren't enough to express the overlaps of error codes and group them sensibly. It would need something like [dhall](https://github.com/dhall-lang/dhall-lang) to do that. 3. I removed 405 errors, because the HTTP method is part of the very spec. If you follow the spec, you can't get 405. <!-- Don't forget to: ✓ Self-review your changes to make sure nothing unexpected slipped through ✓ Assign yourself to the PR ✓ Assign one or several reviewer(s) ✓ Once created, link this PR to its corresponding ticket ✓ Assign the PR to a corresponding milestone ✓ Acknowledge any changes required to the Wiki --> Co-authored-by: Julian Ospald <julian.ospald@iohk.io> Co-authored-by: KtorZ <matthias.benkort@gmail.com>
Is there an introduction somewhere on how this new feature works? |
|
2258: ADP-291: Document endpoints error codes in the API documentation r=hasufell a=hasufell # Issue Number ADP-291 # Implementation approach This is all best-effort. There are no static checks to ensure we didn't miss anything. There's ongoing work at servant to make this possible: haskell-servant/servant#841 But even that would require a major refactor. Testing all possible errors is also an option, but seems quite excessive. The workflow for figuring out the error codes is roughly: 1. follow the entry point to the the `Handler ()` function e.g. ```hs listTransactions :: forall ctx s t k n. (ctx ~ ApiLayer s t k) => ctx ... -> Handler [ApiTransaction n] ``` 2. check all `liftHandler` calls, which have functions with `ExceptT` as argument, e.g.: ```hs listTransactions :: forall ctx s k t. ( HasDBLayer s k ctx , HasNetworkLayer t ctx ) => ctx ... -> ExceptT ErrListTransactions IO [TransactionInfo] ``` 3. find all the LiftHandler instance, e.g. ```hs instance LiftHandler ErrListTransactions where handler = \case ErrListTransactionsNoSuchWallet e -> handler e ErrListTransactionsStartTimeLaterThanEndTime e -> handler e ErrListTransactionsMinWithdrawalWrong -> apiError err400 MinWithdrawalWrong "The minimum withdrawal value must be at least 1 Lovelace." ErrListTransactionsPastHorizonException e -> handler e ``` 4. follow the recursive handlers to resolve all errors and add client error types to `swagger.yaml`. The error code is the 3rd `ApiErrorCode` argument to `apiError`. 5. Some errors are implicit, e.g. failed parameter parsing etc. These are also in `ApiErrorCode`. Also see the special instance `instance LiftHandler (Request, ServerError)` 6. repeat until exhaustion # Overview - [x] Wallets - [x] Addresses - [x] Coin Selections - [x] Transactions - [x] Migrations - [x] Stake Pools - [x] Utils - [x] Network - [x] Proxy - [x] Settings - [ ] Byron-specific endpoints # Remarks 1. I did not double check the byron endpoints. Many of their endpoints share the same types in `swagger.yaml`, so I assumed they have the same behavior wrt error codes. 2. This will generally be hard to maintain, since yaml anchors aren't enough to express the overlaps of error codes and group them sensibly. It would need something like [dhall](https://github.com/dhall-lang/dhall-lang) to do that. 3. I removed 405 errors, because the HTTP method is part of the very spec. If you follow the spec, you can't get 405. <!-- Don't forget to: ✓ Self-review your changes to make sure nothing unexpected slipped through ✓ Assign yourself to the PR ✓ Assign one or several reviewer(s) ✓ Once created, link this PR to its corresponding ticket ✓ Assign the PR to a corresponding milestone ✓ Acknowledge any changes required to the Wiki --> Co-authored-by: Julian Ospald <julian.ospald@iohk.io> Co-authored-by: KtorZ <matthias.benkort@gmail.com>
The idea is that
Instead of having
we could have
which is transformed into something like
Verb
glues RHS and LHS together. This way we canHave checked exceptions (here
"date"
endpoint cannot error!)Get rid of
{-# OVERLAPPING #-}
instances, as inNoContent
doesn't overlapFull a
. Similarly we can haveStream a
andHeaders ... :< ...
.The devil in the details how to fit this into
Router
andDelayedIO
framework we have, but I don't see why it's not possible.
The names of auxiliary class
HasServer'
andFull
combinator are open tobikesheding.
Comments?
The text was updated successfully, but these errors were encountered: