Reputation: 31
Instead of writing something like:
let hello = get()
.and(path!(String))
.and_then(|string| handlers::hello(string).map_err(warp::reject::custom))
.boxed()
I'd like to be able to write:
let hello = get()
.and(path!(String))
.handle(handlers::hello)
where handle
does the and_then
-> map_err
-> boxed
thing.
Right now I use a set of traits for each arity handler. This is the 2-arity handler:
pub trait Handle2<A, B, Fut, R, F>
where
R: Reject,
F: Fn(A, B) -> Fut,
Fut: Future<Output = Result<String, R>>,
{
fn handle(self, f: F) -> BoxedFilter<(String,)>;
}
impl<A, B, Fut, R, F, T, Futr> Handle2<A, B, Fut, R, F> for T
where
R: Reject,
F: Fn(A, B) -> Fut + Clone + Sync + Send + 'static,
Fut: Future<Output = Result<String, R>> + Send,
Futr: Future<Output = Result<(A, B), Rejection>> + Send,
T: Filter<Extract = (A, B), Error = Rejection, Future = Futr> + Sync + Send + 'static,
{
fn handle(self, f: F) -> BoxedFilter<(String,)> {
self.and_then(move |a, b| f(a, b).map_err(warp::reject::custom))
.boxed()
}
}
It's awesome that you can do this in Rust, but is there a simpler way to achieve this?
Upvotes: 2
Views: 1977
Reputation: 4444
There is.
With a route like this one declared in your main():
let routes = get().and(path("hello")).and_then(routes::getRoot);
You can use the async
syntax sugar to auto-transform a simple handler into a future (a 0.3/std one). That makes it implement all the necessary traits to use it as a warp::Filter
:
// routes/mod.rs
pub async fn getRoot() -> Result<impl warp::Reply, warp::Rejection> {
Ok("Hello world !")
}
Pretty simple so far ! Now the one thing you wanted that's still missing is proper error handling with warp::reject::custom
.
First, let's make our handler return an error:
// routes/mod.rs
pub async fn getRoot() -> Result<impl warp::Reply, warp::Rejection> {
let _parsed_url = Url::parse(&"https://whydoesn.it/work?").map_err(ServiceError::from)?;
Ok("Hello world !")
}
Notice the map_err(ServiceError::from)
and the return type warp::Rejection
? We need to turn our specific error into a custom error enum (that's ServiceError, we'll define it soon), and then provide a way to auto-convert a ServiceError into a warp::Rejection.
Let's do that by creating a way to handle errors from all route handler functions:
// ./errors.rs
/// An internal error enum for representing all the possible failure states
#[derive(thiserror::Error, Debug)]
pub enum ServiceError {
#[error("unacceptable !")]
SomeSpecificWayOfFailing,
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error + Sync + Send>), // catchAll error type
}
impl warp::reject::Reject for ServiceError {}
impl From<ServiceError> for warp::reject::Rejection {
fn from(e: ServiceError) -> Self {
warp::reject::custom(e)
}
}
In our case the error example that I gave returns an url::ParseError
.
Let's add a way to map it to a ServiceError:
// ./errors.rs
impl From<url::ParseError> for ServiceError {
fn from(e: url::ParseError) -> Self {
ServiceError::Other(e.into()) // you can refine that and turn it into a more specific enum variant of ServiceError here
}
}
You should be all set. Now adding a new route/handler should be as easy as adding an async function (plus adding impl ServiceError::from
for any new error type that you want to handle in the handler function)
Upvotes: 2