Остановить принудительное использование Rust черты serde :: Deserialize для типа ошибки - PullRequest
0 голосов
/ 22 декабря 2018

Код ниже - это начало небольшой библиотеки, которую я пишу, чтобы поговорить с веб-API.Пользователи библиотеки будут создавать экземпляр клиента MyClient и получать к нему доступ через веб-API.Здесь я пытаюсь получить токен доступа от API, прежде чем отправлять запросы на него.

В get_new_access() Я могу сделать запрос и получить ответ JSON.Затем я пытаюсь использовать serde для преобразования ответа в Access структуру, и именно здесь начинаются проблемы.

Я создал библиотечную ошибку enum MyError, которая может представлять десериализацию JSON иreqwest ошибки, которые могут возникнуть в пределах get_new_access().Однако, когда я иду на компиляцию, я получаю the trait serde::Deserialize<'_> is not implemented for MyError.Насколько я понимаю, это происходит потому, что в случае, если я получаю одну из вышеупомянутых ошибок, serde не знает, как ее десериализовать в структуру Access.Конечно, я вообще не хочу, чтобы это делалось, поэтому у меня вопрос: что мне делать?

Я смотрел на различные примеры десериализации serde, но все они, похоже, предполагают, что ониработает в основной функции, которая может возвращать только ошибку serde.Если я поставлю #[derive(Deserialize)] выше объявления MyError, я получу ту же ошибку, но вместо этого она перейдет на reqwest::Error и serde_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)
    }
}

ссылка на игровую площадку здесь .

1 Ответ

0 голосов
/ 23 декабря 2018

Ваша первая проблема заключается в том, что ваш 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 и веб-запросов;Вы можете объединить результаты, а затем отобразить ошибку для обоих.

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

Примечание. Не объединяйте Result с, если вы хотите распространять разные значения MyError для разных Result с.Вам нужно отобразить ошибку для каждого результата.

...