-
Notifications
You must be signed in to change notification settings - Fork 322
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
Ergonomics issue with enabling ?
in endpoint
#452
Comments
A second thought, given |
Wait that won't work since it implements |
i'm sure there's a really good reason why what is that (probably excellent) reason? |
Ok what I've gathered so far investigating this:
|
@Fishrock So the endpoint has to return a result, and then I guess internally you're taking either the 'Ok' or 'Err' path and converting that to a I would also consider documenting patterns for these more complex cases (returning json body on error, custom status code, etc) to a reasonable "solution". If nothing else it would be part of the design space to consider for future releases |
I have thought quite a bit about it and I understand the reasoning why a result is returned in general, I think the ergonomics break when While the tide error type does allow setting status code and all the funky things there is no way to get this information out of a custom error since it treats all The alternative is to return The next option is to pass it through a custom function that converts the errors, which is possible but as bad as not returning a result in the first place code wise. I feel like the way errors are handled right now prioritizes nice looking example code (where error responses mostly don't matter) over real world applications where error handling is a major consideration. A thought: would it make sense to put the implementation of |
@yoshuawuyts Thoughts? |
I think this is what well have to do. I'm sympathetic to adding more fields from Response to the Error type as well. Probably worth explaining also is that the reason why we introduced a custom error type instead of returning @Licenser we could always add a method to convert from a std error, but we can't add the trait conversion. Coupled with more methods to e.g. set a Body, would that work? I also don't think I quite understand the third paragraph you wrote. Could you elaborate? |
heya sorry I must have written that badly :D. The trait conversion exists here https://github.com/http-rs/http-types/blob/master/src/error.rs#L122 ; Since the conversion goes from a generic Error it always is reported as a Internal Server Error that makes using Wrapping it in a function works, but now all benefits from returning an error are gone, basically, it turns from get(|r| my_function_turning_result_into_Response(my_logic(r))) to get(|r| my_function_turning_my_error_into_http_error(my_logic(r))) The alternative is using my_types_function(input) -> crate::Result {
let decoded = serde_json::from_str(input)?; // I can implement From for crate::Error
let processed = custom_logic(decoded)?; // this function I got control over anyway
let encoded = serde_json::to_string(processed)?; // I can implement From for crate::Error
Ok(encoded);
}
my_types_function(good_stuff) -> http_types::Result {
let decoded = serde_json::from_str(input).map_err(serde_decode_error_to_http_error)?;
let processed = custom_logic(decoded)?;
let encoded = serde_json::to_string(processed).map_err(serde_decode_error_to_http_error)?;
Ok(encoded);
} This really makes it very ergonomic. Of cause this, as much as the current implementation, is base on assumptions how people use it so I want to be clear about the assumptions I make:
to 3) Looking at the docs there is this example: #[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Counter { count: usize }
let mut app = tide::new();
app.at("/").get(|mut req: Request<()>| async move {
let mut counter: Counter = req.body_json().await?;
println!("count is {}", counter.count);
counter.count += 1;
Ok(Response::new(tide::http::StatusCode::Ok).body_json(&counter)?)
});
app.listen("127.0.0.1:8080").await?; This read nice as an example, and it feels like there was the focus when designing the way requests work right now, that's great to get users and make it look slick but it's followed by some disappointment. I think most of us would not like to put this code in production since, if it gets invalid JSON as an input it will fail with a 500 error instead of a 400/415 indicating the data is wrong. The downside of dropping the |
Surely if you add more methods to the error type to set the body, or status code, without changing the Endpoint signature you're still losing the benefit of the 'try' operator anyway, right? For me personally it would be ideal if the Endpoint returned a This would be more powerful and customisable for any real use cases, and really only affect the the ergonomics of the noddy examples in the docs (I'm aware those pretty examples are an important part of this crate's ethos. Those pretty examples and the accompanying blog posts are the reason I'm here after all!) You'd force users to write The end result is I could write my custom conversion and then happily use the try operator directly in the endpoint. |
For most applications customizing the error code should be a matter of doing the following: use tide::prelude::*;
async fn my_endpoint(_: Request<()>) -> tide::Result {
let file = fs::read("path-to-my-file").status(401)?;
let res = process(file).await?;
Ok(res)
} Status codes can readily be set in most cases.
You're right this should be a 4xx range error. The fact that it isn't is a bug. We discovered a similar case for query two days ago as well http-rs/http-types#154. If you find a place where we're returning what seems to be a wrong status code, filing a bug would be appreciated! To clarify: we're unlikely to change the signature of the our functions anytime soon. I'm trying to understand better what we can do to improve error handling within our existing framework. What I've gathered so far is:
|
👍 I'll make PR's when I find them :) the i.e. the above example would return 401 if the file can't be read, also if it is missing (that probably should be 404) as well as if the FS is broken (let's hope that doesn't happen but it should be a real 500). Now since fs returns it's own error and is neither used nor tide/http_types it can't implement it's own From but if it were a custom call all those 4xx/5xx could be part of a error conversion.
While my initial thought was the signature is an issue, during the course of the discussion I am now convinced I was wrong with that. The signature is perfectly fine. Where I see friction is the auto conversion from Playing it through, with no use tide::prelude::*;
async fn my_endpoint(_: Request<()>) -> tide::Result {
// This still works since .status() will probably translate
let file = fs::read("path-to-my-file").status(401)?;
// this would either require crate::Error to implement Into<tide::Error> and then get proper
// error codes from that
let res = process(file).await?;
// or uses the .status() function to just make it 500 as it is now but it also makes it more obvious
// that errors here are not handled with much detail
let res = process(file).await.status(500)?;
Ok(res)
} EDIT: Perhaps a good solution would be to put the from for |
adding a I think putting the auto conversion behind an opt-out feature gate would satisfy all the more complex use cases. It's kind of the nuclear option though. you might only have one endpoint where you need the increased flexibility, and suddenly you're manually handling errors across your whole application. |
Related to all of the following discussions, it would be useful for http_types's errors to optionally carry data similar to a response, particulaly Headers and/or a Body. This allows for better handling of situations where things like middleware want to act only partialy on an error. Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
Related to all of the following discussions, it would be useful for http_types's errors to optionally carry data similar to a response, particularly Headers and/or a Body. This allows for better handling of situations where things like middleware want to act only partially on an error. Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retreival via `error() -> Option<&Error>`. Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. PR-URL: http-rs#174 Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
Hi folks, could you please let us know if http-rs/http-types#174 and/or #570 would help you at all? tl;dr:
|
I don't think it would, the breaking point of all that is still |
I've been thinking a lot about error handling lately, and my hope is that the solution @Fishrock123 described will address the needs of real-world messy production applications with lots of error handling needs. Currently, the idea is that every endpoint returns tide::Result. When you use The fact that Endpoints return Result should be considered a workaround for the fact that stable rust does not allow us to implement Try as a trait yet. The approach described in this comment always immediately converts the contents of an Err variant into a Response that contains the tide::Error, which in turn contains the original Err content that ? was called on. As a result (no pun intended), tide::Error can be optionally be considered an implementation detail, and as long as you return tide::Result::Err(dyn std::error::Error + Send + Sync + 'static) or use ? to do so, you'll be able to get that error back out if you know its type, and you'll also be able to provide blanket handlers for Errors of types that aren't explicitly handled. For anyone with concerns about error handling (@Licenser, @danieleades): If you have time for it, a minimal example repo of an app with the sort of custom error types that you're looking to use with Tide would help us ensure that tide's error handling approach allows for an ergonomic, DRY, and concise way of expressing custom error → Response mappings. Hopefully, this shouldn't require any mapping that's repeated for each route handler / endpoint. |
@jbr I put together an example here: https://github.com/Licenser/tide-ergo Please note this is a synthetic example and not perfect, also quite small so changes are "easy" compared to an impact on a large codebase with thousands of lines of code and more than 4 dependencies :P and 2 functions. I also add a branch no-std-err in with an 'assumption' that the result would look like: |
@Licenser I updated that for the proposed error handling style here https://github.com/Licenser/tide-ergo/blob/10d41ffaf555b596d88910c62d1f8442b10280a0/src/main.rs |
Heya @jbr I see where you are going with this change :) that wasn't obvious me in the beginning, sorry. Thank you for spending the time to demonstrate and explain your idea! I'd say that would be a nice improvement! The only concern would be performance if there are many downcast happening, but that's a different topic then ergonomics and at least for me, or my current use-case, not relevant. |
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. PR-URL: http-rs#174 Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. PR-URL: http-rs#174 Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
Is this still an issue in Tide 0.12? (i.e. with #570) |
It's just a |
The way
?
is now supported in Endpoint introduces some issues for more complex applications that do not use anonymous functions or need some control over how errors are transmitted.To prefix this, I don't think there is a clear right or wrong in this. Allowing
?
in endpoints is a definite benefit for some use cases as the examples shared on twitter and other channels. However, I have the feeling that the impact on more complex API's was considered.I want to start with a before -> after going from 0.6 to 0.7.
This is w/ tide 0.6 (and a custom fix to implement IntoResponse for Result). It is rather elegant to write:
After migrating to 0.7 (with the same custom extension to IntoResponse) it looks like this. Which is rather painful to both read and write it introduces a whole lot of boilerplate that wasn't required before and adds quite a bit of redundancy.
A few things that do not work and the limitations that we ran into:
The
http_types::Error
doesn't allow setting header types so it is not used for any API that requires, header. Either custom error headers or things like Content-Type. (I think this is fixable by extendinghttp_types::Error
, but there might be other issues or requirements I didn't think of like the need for async downloads or things that will make usinghttp_types::Error
in some cases just not possible).Returning
http_types::Error
into the functions such asapi::version::get
doesn't solve the problem just moves it. Since it's not possible to implementInto<http_types::Error>
for 3rd party Errors using?
for anything that isn't owned by the crate would become a no-go, while intermediating over a custom error type does allow doing this.bouncing every call through a 'translation' function. This would work but really breaks ergonomics IMHO example
I think the tension here is between making something look good in an example and easy to use for simple applications and making something powerful for more complex applications. I honestly don't know what's the "right" answer.
Perhaps returning to not reqiering a Result in Endpoint and instead implementing
From<Result<T, E>>
for Response would allow a middle ground that is only slightly more painful for simple applications along the lines of this would work?The text was updated successfully, but these errors were encountered: