【问题标题】:Stop Rust from enforcing the serde::Deserialize trait on an error type阻止 Rust 对错误类型强制执行 serde::Deserialize 特征
【发布时间】:2019-05-22 16:24:20
【问题描述】:

下面的代码是我为与 Web API 对话而编写的一个小型库的开始。该库的用户将实例化一个客户端 MyClient 并通过它访问 Web API。在这里,我尝试在向 API 发出请求之前从 API 获取访问令牌。

get_new_access() 中,我可以发出请求并接收 JSON 响应。然后我尝试使用 serde 将响应转换为 Access 结构,这就是问题的开始。

我创建了一个库特定的错误枚举MyError,它可以表示get_new_access() 中可能发生的 JSON 反序列化和 reqwest 错误。但是,当我去编译时,我得到了the trait serde::Deserialize<'_> is not implemented for MyError。我的理解是这种情况正在发生,因为在我遇到上述错误之一的情况下,serde 不知道如何将其反序列化为 Access 结构。当然,我根本不希望它这样做,所以我的问题是我该怎么办?

我查看了各种 serde 反序列化示例,但它们似乎都假设它们在只能返回 serde 错误的主函数中运行。如果我将#[derive(Deserialize)] 放在MyError 的声明之上,那么我会得到同样的错误,但它会转移到reqwest::Errorserde_json::Error

use std::error;
use std::fmt;

extern crate chrono;
extern crate reqwest;

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use chrono::prelude::*;
use reqwest::Client;

pub struct MyClient {
    access: Access,
    token_expires: DateTime<Utc>,
}

#[derive(Deserialize, Debug)]
struct Access {
    access_token: String,
    expires_in: i64,
    token_type: String,
}

fn main() {
    let sc: MyClient = MyClient::new();

    println!("{:?}", &sc.access);
}

impl MyClient {
    pub fn new() -> MyClient {
        let a: Access = MyClient::get_new_access().expect("Couldn't get Access");
        let e: DateTime<Utc> = chrono::Utc::now(); //TODO
        MyClient {
            access: a,
            token_expires: e,
        }
    }

    fn get_new_access() -> Result<Access, MyError> {
        let params = ["test"];
        let client = Client::new();
        let json = client
            .post(&[""].concat())
            .form(&params)
            .send()?
            .text()
            .expect("Couldn't get JSON Response");

        println!("{}", &json);

        serde_json::from_str(&json)?

        //let a = Access {access_token: "Test".to_string(), expires_in: 3600, token_type: "Test".to_string() };

        //serde_json::from_str(&json)?
    }
}

#[derive(Debug)]
pub enum MyError {
    WebRequestError(reqwest::Error),
    ParseError(serde_json::Error),
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "eRROR")
    }
}

impl error::Error for MyError {
    fn description(&self) -> &str {
        "API internal error"
    }

    fn cause(&self) -> Option<&error::Error> {
        // Generic error, underlying cause isn't tracked.
        None
    }
}

impl From<serde_json::Error> for MyError {
    fn from(e: serde_json::Error) -> Self {
        MyError::ParseError(e)
    }
}

impl From<reqwest::Error> for MyError {
    fn from(e: reqwest::Error) -> Self {
        MyError::WebRequestError(e)
    }
}

游乐场链接here

【问题讨论】:

  • 请发送正确的minimal reproducible example。该代码中缺少语法错误和其他部分,这使我们无法重现该问题。特别是,您提到了MyError 类型,并在您未提供的main 函数中使用了MyClient。考虑制作一些在Rust Playground 中运行并显示相同问题的东西。 rust tag info 中也有 Rust 特定的提示。
  • 更新了代码以重现错误和游乐场链接
  • minimal reproducible example 中的所有单词都很重要。 Minimal 是第一个。

标签: rust serde


【解决方案1】:

您的第一个问题是您的fn get_new_access() -&gt; Result&lt;Access, MyError&gt; 需要Result。但在这里:

    //...
    serde_json::from_str(&json)?
}

由于使用了?(try 宏),您正试图返回Result 的展开值,它是serde::Deserialize&lt;'_&gt; 的子类型。编译器会警告您此 Deserialize 不是 Result。你应该做的只是返回结果而不打开它:

    //...
    serde_json::from_str(&json)
}

或者

    //...
    let access = serde_json::from_str(&json)?; // gets access or propagates error 
    Ok(access) //if no error return access in a Result
}

然后您将遇到第二个问题,因为您的函数在 Result 中需要 MyError,而您通过此调用 serde_json::from_str(&amp;json) 返回 serde_json::Error。幸运的是 Result 有函数 map_err 它将实际错误类型映射到您的自定义错误类型。

此代码将解决您的问题:

    //...
    serde_json::from_str(&json).map_err(MyError::ParseError)
}

对于comment中的请求:

例如,如果我将网络请求行更改为let json = client.post("").form(&amp;params).send().map_err(MyError::WebRequestError)?.text()?;, 那是更好的做法吗?

是的,但是由于text() 返回Result,因此您也需要将其错误映射为MyError。由于sendtext 具有相同的错误类型(reqwest::Error),您可以将结果与and_then 结合起来:

let json = client
    .post(&[""].concat())
    .form(&params)
    .send()
    .and_then(Response::text) //use reqwest::Response; 
    .map_err(MyError::WebRequestError)?;

【讨论】:

  • 非常感谢! map_err 似乎是我遗漏的细节。完成后,从“良好的 Rust 风格”的角度来看,关于我的错误实现等,我的实现是否“正确”?如果没有,我该怎么办?
  • 例如,如果我将网络请求行更改为let json = client.post("").form(&amp;params).send().map_err(MyError::WebRequestError)?.text()?;,那是更好的做法吗?
猜你喜欢
  • 2019-11-03
  • 2021-03-30
  • 2020-05-05
  • 2020-03-31
  • 2021-06-07
  • 1970-01-01
  • 2020-08-17
  • 2020-12-31
  • 2022-07-31
相关资源
最近更新 更多