The code below is the beginnings of a small library I'm writing to talk to a web API. Users of the library will instantiate a client MyClient
and access the web API through it. Here, I'm trying to get an access token from the API before making requests to it.
In get_new_access()
I'm able to make the request and receive the JSON response. I then try to use serde to turn the response into an Access
struct, and this is where the problems start.
I've created a library specific error enum MyError
which can represent the JSON deserializing and reqwest errors that could occur within get_new_access()
. However, when I go to compile I get the trait serde::Deserialize<'_> is not implemented for MyError
. My understanding is that this is happening because in the case that I get one of the aforementioned errors, serde does not know how to deserialize it into an Access
struct. Of course, I don't want it to do that at all, so my question is what should I do?
I've looked at various serde deserialize examples, but all of them seem to assume that they are running in a main function that can only return a serde error. If I put #[derive(Deserialize)]
above MyError
's declaration, then I get the same error, but it shifts to reqwest::Error
and serde_json::Error
instead.
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(¶ms)
.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)
}
}
Playground link here.
Your first problem is that your fn get_new_access() -> Result<Access, MyError>
expects a Result
. But in here:
//...
serde_json::from_str(&json)?
}
because of using ?
(try macro), you are trying to return Result
's unwrapped value which is a subtype of serde::Deserialize<'_>
. The compiler warns you about this Deserialize
is not a Result
. What you should do is just return the result without unwrapping it:
//...
serde_json::from_str(&json)
}
Or
//...
let access = serde_json::from_str(&json)?; // gets access or propagates error
Ok(access) //if no error return access in a Result
}
Then you will have a second problem because your function expects MyError
in the Result
while you are returning serde_json::Error
with this call serde_json::from_str(&json)
. Luckily Result
has the function map_err
which maps the actual error type to your custom error type.
This code will solve your problem:
//...
serde_json::from_str(&json).map_err(MyError::ParseError)
}
For the request in the comment :
For example, if I change the web request line to
let json = client.post("").form(¶ms).send().map_err(MyError::WebRequestError)?.text()?;
, is that better practice at all?
Yes but since text()
returns a Result
you need to map it's error as MyError
too. Since both send
and text
has same error type(reqwest::Error
) you can combine the results with and_then
:
let json = client
.post(&[""].concat())
.form(¶ms)
.send()
.and_then(Response::text) //use reqwest::Response;
.map_err(MyError::WebRequestError)?;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With