阻止Rust在错误类型上强制执行serde :: Deserialize特性

时间:2018-12-22 16:57:27

标签: rust serde

下面的代码是我正在写的用于与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错误的main函数中运行。如果我将#[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

1 个答案:

答案 0 :(得分:4)

您的第一个问题是您的fn get_new_access() -> Result<Access, MyError>期望Result,因为您使用了问号:

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

您正在尝试返回Result的未包装值,该值是serde::Deserialize<'_>的子类型。编译器会警告您 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
}

您将遇到第二个问题,因为您的函数期望MyError中有Result。要解决此问题,您可以将错误映射到您的错误类型:serde_json::from_str(&json)返回一个Result<T, serde_json::Error>,幸运的是Result具有函数map_err可以映射您当前的错误。错误类型更改为您的自定义错误类型。

此代码将解决您的问题:

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

对于评论请求:

  

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

是的,但是您也需要映射text()的错误,如果要对JSON解析和Web请求使用常见的错误类型;您可以合并结果,然后为两者映射错误。

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

注意:如果要传播不同的Result的不同MyError值,请不要合并Result。您需要为每个结果映射错误。