7

使用warp.rs 0.2.2,让我们考虑一个基本的 Web 服务,它有一个路由GET /

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
    let getRoot = warp::get().and(warp::path::end()).and_then(routes::getRoot);
    warp::serve(getRoot).run(([0, 0, 0, 0], 3030)).await;
    Ok(())
}

我的目标是?用于路由处理程序中的错误处理,所以让我们编写一个可以错误并提前返回的程序crate::routes

use crate::errors::ServiceError;
use url::Url;

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

这个版本有效。这里返回的错误Url::parse()url::ParseError

为了在错误类型之间进行转换,从url::ParseErrorto ServiceError,然后从ServiceErrorto warp::Rejection,我写了一些错误助手crate::errors

#[derive(thiserror::Error, Debug)]
pub enum ServiceError {
    #[error(transparent)]
    Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}
impl warp::reject::Reject for ServiceError {}
impl From<ServiceError> for warp::reject::Rejection {
    fn from(e: ServiceError) -> Self {
        warp::reject::custom(e)
    }
}
impl From<url::ParseError> for ServiceError {
    fn from(e: url::ParseError) -> Self {
        ServiceError::Other(e.into())
    }
}

现在,上述方法有效,我正在尝试缩短第二个代码块以?直接用于错误处理,并自动从底层错误(此处url::ParseError)转换为warp::Rejection. 这是我尝试过的:

use crate::errors::ServiceError;
use url::Url;

pub async fn getRoot() -> Result<impl warp::Reply, ServiceError> {
    let _parsed_url = Url::parse(&"https://whydoesn.it/work?")?;

    Ok("Hello world !")
}

url::ParseError返回的将Url::Parse很好地转换为 ServiceError 以返回,但从我的处理程序返回 ServiceError 不起作用。我得到的第一个编译错误是:

error[E0277]: the trait bound `errors::ServiceError: warp::reject::sealed::CombineRejection<warp::reject::Rejection>` is not satisfied
   --> src/main.rs:102:54
    |
102 |     let getRoot = warp::get().and(warp::path::end()).and_then(routes::getRoot);
    |                                                      ^^^^^^^^ the trait `warp::reject::sealed::CombineRejection<warp::reject::Rejection>` is not implemented for `errors::ServiceError`

有没有一种方法可以保持简短的错误处理?只使用和:

  • ServiceError实施warp::reject::sealed::CombineRejection<warp::reject::Rejection>?_
  • 解决这个问题?
4

2 回答 2

3

您可以实现From将错误类型转换为warp::Rejectionusing reject::customRejection封装自定义类型,您稍后可以选择在recover处理程序内部进行检查。

这个例子使用了一个普通的错误结构,但如果你有一个错误枚举,你可以匹配恢复处理程序中的变体,并根据需要执行不同的逻辑。

use serde::Deserialize;
use snafu::{ensure, Snafu};
use std::convert::Infallible;
use warp::{
    filters::{any, query, BoxedFilter},
    http::StatusCode,
    reject::Reject,
    Filter, Rejection, Reply,
};

// A normal error type, created by SNAFU
#[derive(Debug, Snafu)]
#[snafu(display("Expected a value less than 10, but it was {}", value))]
struct LessThanTenError {
    value: i32,
}

// A function that might fail
fn validate(value: i32) -> Result<i32, LessThanTenError> {
    ensure!(value < 10, LessThanTenContext { value });
    Ok(value)
}

// We need a custom type to later extract from the `Rejection`. In
// this case, we can reuse the error type itself.
impl Reject for LessThanTenError {}

// To allow using `?`, we implement a conversion from our error to
// `Rejection`
impl From<LessThanTenError> for Rejection {
    fn from(other: LessThanTenError) -> Self {
        warp::reject::custom(other)
    }
}

#[tokio::main]
async fn main() {
    let api = simple_math().recover(report_invalid);

    let p: std::net::SocketAddr = "0.0.0.0:8888".parse().unwrap();
    warp::serve(api).run(p).await;
}

#[derive(Debug, Deserialize)]
struct QueryParams {
    a: i32,
    b: i32,
}

fn simple_math() -> BoxedFilter<(impl Reply,)> {
    any::any()
        .and(query::query())
        .and_then(|args: QueryParams| async move {
            // Look at us using those question marks!
            let a = validate(args.a)?;
            let b = validate(args.b)?;
            let sum = validate(a + b)?;

            // We specify that we are returning an error type of
            // `Rejection`, which allows the compiler to know what
            // type to convert to when using `?` here.
            Ok::<_, Rejection>(format!("The sum is {}", sum))
        })
        .boxed()
}

async fn report_invalid(r: Rejection) -> Result<impl Reply, Infallible> {
    if let Some(e) = r.find::<LessThanTenError>() {
        // It was our specific error type, do whatever we want. We
        // will just print out the error text.
        Ok(warp::reply::with_status(
            e.to_string(),
            StatusCode::BAD_REQUEST,
        ))
    } else {
        // Do prettier error reporting for the default error here.
        Ok(warp::reply::with_status(
            String::from("Something bad happened"),
            StatusCode::INTERNAL_SERVER_ERROR,
        ))
    }
}
[dependencies]
serde = { version = "1.0.118", features = ["derive"] }
snafu = "0.6.10"
tokio = { version = "0.2.23", features = ["full"] }
warp = "0.2.5"
% curl 'http://127.0.0.1:8888'
< HTTP/1.1 500 Internal Server Error
Something bad happened

% curl -v 'http://127.0.0.1:8888?a=1&b=2'
< HTTP/1.1 200 OK
The sum is 3

% curl -v 'http://127.0.0.1:8888?a=6&b=5'
< HTTP/1.1 400 Bad Request
Expected a value less than 10, but it was 11

也可以看看:

于 2020-12-07T03:52:17.300 回答
1

根据我的发现,有两种解决方案。

  1. ?如果出现错误,请放弃使用您自己的宏来构造并返回响应。

  2. 使用cjbassi 的PR #458而不是主线版本:

    • 在您的错误类型上实施warp::reply::Reply,以便将其转换为面向用户的正确错误消息。
    • 在 Cargo.toml 文件中替换warp = "0.2"warp = { git = "https://github.com/cjbassi/warp.git", branch = "error"}
    • 使用.map_async而不是.and_then处理程序
于 2020-07-23T17:09:33.717 回答