Какой самый идиоматичный способ обработки ошибок в асинхронных обработчиках в actix-web?

У меня есть асинхронный обработчик в actix_web, который должен завершиться ошибкой, если несколько заголовков не установлены. Я не понимаю, какой лучший способ обработки ошибок в функциях, которые возвращают Future должно быть. Я в основном хочу эквивалент ? Оператор по фьючерсам.

Это мой текущий код:

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()
    },
);

Я решил отсутствие ? оператор для фьючерсов путем сопоставления с ранним возвратом. Однако в моем коде мне нужно убедиться, что существует множество других заголовков.

В идеале я хотел бы извлечь логику сопоставления и раннего возврата к чему-то повторно используемому, но в этом случае это заставляет меня создавать макрос. Это кажется излишним, особенно если в языке уже есть что-то, что позволяет мне делать то, что я хочу.

Какой самый идиоматический способ справиться с этой ситуацией?

1 ответ

Решение

Чтобы обработать ошибки, верните ошибочный Future, Например, проверьте заголовок как Future а затем соедините ваше будущее с .and_then, Хитрость заключается в том, чтобы сохранить типы ошибок фьючерсов одинаковыми, чтобы избежать map_err, Например:

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)
}

Результат:

$ curl localhost:8000
invalid x-client-id header⏎
$ curl localhost:8000 -H 'x-client-id: asdf'
asdf⏎

когда operation_that_returns_future имеет другой тип ошибки:

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))
}

Еще одна хитрость заключается в использовании ящика с ошибками, который обеспечивает failure::Error::from который отображает все ошибки в один тип, failure::Error,

Наконец, вы можете найти actix_web::guards полезно для проверки значений заголовка:

.guard(guard::Header("x-client-id", "special client"))
Другие вопросы по тегам