Skip to content
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

Default error handling #739

Open
JonasHedEng opened this issue Nov 10, 2020 · 8 comments
Open

Default error handling #739

JonasHedEng opened this issue Nov 10, 2020 · 8 comments

Comments

@JonasHedEng
Copy link

We recently bumped our Tide version from 0.8.1 to 0.14.0 which caused our response payloads/bodies for errors to be empty. This seems to be since we use tide::Errors for our "web-facing" results and due to an Into conversion that lose/discard the embedded error. In the tide::Result -> http_types::Response Into implementation, which discards the error that is converted into a Response here.

From the discussion in #452, which seems to be very similar to our issue, I understand it as you want to prevent people from accidentally returning internal error messages/details by not creating the full response from the error?

It would be great to have some way of using tide::Error for our user-facing errors without having to attach our own After middleware just to map the error message into the response body when our tide::Error already contains all the necessary data (status code + error message). Thanks!

@Fishrock123
Copy link
Member

Fishrock123 commented Nov 10, 2020

Hmm. Maybe we should have a default client-error-handling middleware feature. It's hard to know what users will want though - raw text? Json? Maybe based on Accepts?

However if you want to have response bodies for 5XX errors you will have to do that yourself. We will never do that, due to security issues with exposing internal error information.

@Fishrock123 Fishrock123 changed the title Unergonomic error handling with tide::Error Default error handling Nov 11, 2020
@Fishrock123
Copy link
Member

Fishrock123 commented Nov 11, 2020

We recently bumped our Tide version from 0.8.1 to 0.14.0 which caused our response payloads/bodies for errors to be empty.

Ah I did not notice that. Yes this is expected, that is an intentional change that was, I believe, introduced in tide 0.12. This is, as started above, to prevent internal server errors from leaking out information to external clients.

@JonasHedEng
Copy link
Author

JonasHedEng commented Nov 17, 2020

Not embedding the payload on 5XX responses makes sense but it would be nice to have a default handler for the other error responses. I'm not sure how to decide on what content type, maybe have some default content type configurable in the Server which can be "overridden" by the Accepts header?

And thanks for updating the titel to be more descriptive!

@ohmree
Copy link

ohmree commented Dec 20, 2020

What I understood from this issue is that something like setting the response header on error from a middleware (in a way that's applied automatically to all routes using it) isn't possible yet, am I correct?
I'm wrapping a rate-limiter crate in a middleware and would like to optionally respond with the proper Retry-After header if the rate-limiter decides the user needs to wait.

@yoshuawuyts
Copy link
Member

What I understood from this issue is that something like setting the response header on error from a middleware (in a way that's applied automatically to all routes using it) isn't possible yet, am I correct?

This can be done by creating a middleware which matches all 4xx and 5xx status codes, and then setting the appropriate headers. Tide supports both global and per-route middleware. To set a global middleware use tide::Server::with:

let mut app = tide::new();
app.with(MyMiddleware::new());
app.listen("localhost:8080").await?;

@ohmree
Copy link

ohmree commented Dec 24, 2020

This can be done by creating a middleware which matches all 4xx and 5xx status codes, and then setting the appropriate headers. Tide supports both global and per-route middleware. To set a global middleware use tide::Server::with:

Hmm, the waiting time is only acquirable from the rate limiter struct that's a member of my middleware, and only in the case when there actually is any rate limiting (rate_limiter.check() returns an Err(negative_time_value)) , which I believe limits what I can do in a global After middleware.

Unless there's a way of getting access to the middleware's data after the with, then I might be able to hack something together but it won't be particularly idiomatic.

I could maybe have one static rate limiter but then the middleware can't be used on a per-route basis which could be desirable even though I specifically don't need it.

Another option would be to use a mutable static container of rate limiters but I'd much rather not have to write any unsafe code.

EDIT: I feel a bit stupid, I ended up just making a new response res with the proper headers and body and returning Ok(res) when the rate limit is reached.
It does feel a bit weird to return Ok on failure but since it isn't really an error I guess it's the right thing to do.
The logging middleware also shows it in yellow which seems correct, since there's no actual error in the server.

@Fishrock123
Copy link
Member

@mdtusz
Copy link

mdtusz commented Jul 14, 2021

Another option which I've been using is a simple utility for creating Ok(response)s so that Err is only returned from handlers that have had unhandled or unexpected errors.

pub fn error<S, D>(status: S, message: &str, data: D) -> Response
where
    S: TryInto<StatusCode> + Copy,
    S::Error: Debug,
    D: Serialize,
{
    let json_blob = json!({
        "status": "error",
        "message": message,
        "data": data
    });

    let body = Body::from_json(&json_blob).unwrap();
    let mut resp = Response::builder(status).body(body).build();

    let error = tide::Error::from_str(status, message.to_string());
    resp.set_error(error);

    resp
}

// In some handler
let resp = error(400, "You've done something wrong!", ());
Ok(resp)

This allows us to construct error responses in handlers and return them as Ok(some_error) while still retaining lots of flexibility on which data is actually returned in an error response, and any unexpected errors can be handled generically since they only have the status code (which typically means it's an "unexpected" 5xx error that the client can't handle or know about anyways).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants