Reputation: 1433
I have an async handler in actix_web that must fail if several headers are not set. I don't understand what the best way of handling errors in functions that return Future
should be. I basically want an equivalent of the ?
operator for futures.
This is my current code:
r.post().with_async(
move |req: HttpRequest, path: Path<EventPath>, body: Json<EventCreationRequest>| {
let headers = req.headers();
let client_id = match headers
.get("x-client-id")
.ok_or("Header not found")
.and_then(|v| v.to_str().map_err(|_| "Invalid header content"))
{
Err(e) => return ok(HttpResponse::BadRequest().body(e)).responder(),
Ok(v) => v.to_string(),
};
operation_that_returns_future()
.map(|_| HttpResponse::Ok().body("OK!"))
.responder()
},
);
I have solved the lack of a ?
operator for futures by matching an doing an early return. However, in my code I actually need to ensure that a bunch of other headers exist.
Ideally, I would like to extract the matching and early return logic to something reusable, but in this case that forces me to create a macro. That seems like a bit of an overkill, especially if there's already something in the language that allows me to do what I want.
What is the most idiomatic way of handling this situation?
Upvotes: 2
Views: 819
Reputation: 3647
To handle errors, return a failing Future
. For example, do the header check as a Future
, then chain your futures with .and_then
. A trick is to keep error types of futures the same, to avoid map_err
. For example:
fn handler(req: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> {
has_client_header(&req)
.and_then(|client| operation_that_returns_future(client))
.map(|result| HttpResponse::Ok().body(result))
}
fn has_client_header(req: &HttpRequest) -> impl Future<Item = String, Error = Error> {
if let Some(Ok(client)) = req.headers().get("x-client-id").map(|h| h.to_str()) {
future::ok(client.to_owned())
} else {
future::failed(ErrorBadRequest("invalid x-client-id header"))
}
}
fn operation_that_returns_future(client: String) -> impl Future<Item = String, Error = Error> {
future::ok(client)
}
Result:
$ curl localhost:8000
invalid x-client-id header⏎
$ curl localhost:8000 -H 'x-client-id: asdf'
asdf⏎
When operation_that_returns_future
has another error type:
fn handler(req: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> {
has_client_header(&req)
.and_then(|client| {
operation_that_returns_future(client)
.map_err(|_| ErrorInternalServerError("operation failed"))
})
.map(|result| HttpResponse::Ok().body(result))
}
Another trick is to use the failure crate, which provides failure::Error::from
that maps all errors to one type, failure::Error
.
Lastly, you might find actix_web::guards
useful for checking header values:
.guard(guard::Header("x-client-id", "special client"))
Upvotes: 3