【问题标题】:How do I handle errors in Warp using both Rejection and the question-mark operator?如何使用拒绝和问号运算符处理 Warp 中的错误?
【发布时间】:2020-07-15 16:58:14
【问题描述】:

使用warp.rs 0.2.2,让我们考虑一个基本的网络服务,其中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::ParseErrorServiceError,然后从ServiceErrorwarp::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::Parse 返回的 url::ParseError 将转换为 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&lt;warp::reject::Rejection&gt;
  • 解决这个问题?

【问题讨论】:

    标签: http error-handling rust rust-warp


    【解决方案1】:

    您可以使用reject::custom 实现From 以将您的错误类型转换为warp::RejectionRejection 封装了自定义类型,您以后可以选择在 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
    

    另见:

    【讨论】:

      【解决方案2】:

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

      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-09-18
        • 2017-05-11
        • 1970-01-01
        • 2021-05-11
        • 2016-02-19
        • 1970-01-01
        • 2017-06-08
        • 2015-10-08
        相关资源
        最近更新 更多