Error Handling with Rejections
In warp
, not all errors are final. The framework distinguishes between recoverable errors (rejections) and non-recoverable errors (which typically become a 500 Internal Server Error
). This system allows for flexible and composable error handling.
What is a Rejection
?
A Rejection
is a way for a Filter
to signal that it cannot process a request. It's not necessarily a client or server error. Instead, it means "this filter's requirements were not met, so perhaps another filter can handle it."
Common built-in rejections include:
- A path segment doesn't match (
warp::path
). - The request method is incorrect (
warp::get
). - A required header is missing (
warp::header
). - The request body fails to deserialize (
warp::body::json
).
When a filter in an or()
chain is rejected, warp
simply tries the next filter. If all filters are rejected, warp
converts the most relevant rejection into an appropriate HTTP error response (e.g., 404 Not Found, 405 Method Not Allowed).
Custom Rejections
You can create your own rejection types to represent specific application-level failures. A custom rejection must be a struct that implements the warp::reject::Reject
trait.
use warp::reject;
// A custom rejection for when a denominator is zero.
#[derive(Debug)]
struct DivideByZero;
// Implementing Reject allows it to be used with warp::reject::custom().
impl reject::Reject for DivideByZero {}
// You can then reject a request with it:
async fn div_by(n: u16) -> Result<u16, reject::Rejection> {
if n == 0 {
Err(reject::custom(DivideByZero))
} else {
Ok(n)
}
}
Recovering from Rejections
To provide custom error responses, you use the Filter::recover()
combinator. It takes a closure that receives a Rejection
and must return a Result<impl Reply, Infallible>
.
This allows you to inspect the rejection and decide how to respond.
use std::convert::Infallible;
use warp::{Filter, Rejection, Reply, http::StatusCode};
// (Assuming DivideByZero from the previous example)
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
if err.is_not_found() {
// Handle 404 Not Found
Ok(warp::reply::with_status("NOT_FOUND", StatusCode::NOT_FOUND))
} else if let Some(DivideByZero) = err.find() {
// Handle our custom rejection
Ok(warp::reply::with_status("DIVIDE_BY_ZERO", StatusCode::BAD_REQUEST))
} else {
// For any other rejection, log it and return a generic 500.
eprintln!("unhandled rejection: {:?}", err);
Ok(warp::reply::with_status("INTERNAL_SERVER_ERROR", StatusCode::INTERNAL_SERVER_ERROR))
}
}
let routes = some_filter
// ...
.recover(handle_rejection);
Inspecting Rejections
Inside a recover
handler, you have several tools:
err.is_not_found()
: Checks if the rejection is for a route that didn't match.err.find::<T>()
: Tries to find a specific custom rejection cause of typeT
within the accumulated rejections.- You can also match against specific built-in rejection types, like
warp::filters::body::BodyDeserializeError
, to provide more detailed error messages to the client.
This system allows you to centralize your error handling logic and provide consistent, user-friendly error responses for your entire API.