Deserialize reqwest query error rustc(E0308)

Viewed 125

Not sure why is this happening, here's a snippet:

struct:

use serde::Deserialize;

#[derive(Deserialize, Debug)]
pub struct Snapshot<'a> {
    id              : u32,
    some_data       : &'a str
}

impl<'a> SomeStruct{
    async fn snapshot(url: &str) -> Result<Snapshot<'a>, Box<dyn std::error::Error>> {
        let resp = reqwest::get(&url).await?;
        
        let snapshot = resp.json::<Snapshot>().await?;

        println!("{:?}", resp);
        Ok(snapshot)
    }
}

error message: on the json line

mismatched types

one type is more general than the other

note: expected trait `models::requests::_::_serde::Deserialize<'de>`
         found trait `models::requests::_::_serde::Deserialize<'_>`rustc(E0308)

I imagine this has something to do with the lifecycle of the elements on struct snapshot, but I'm not sure how to properly fix it.

Cargo.toml:

[dependencies]
actix-web = "3"
reqwest = { version = "0.11.6", features = ["json"] }
async-trait = "0.1.51"
serde = "1.0.130"
1 Answers

The concrete problem is that

resp.json::<Snapshot>()

elides the lifetime on Snapshot, so you won't be deserializing to Snapshot<'a>.

But even if you fix that, this cannot work: Your deserialized snapshot does not have anywhere it could borrow its &'a str from. The only possibility is resp, which is dropped at the end of the snapshot function, so you can't return the deserialized snapshot. Without more knowledge about the surrounding code, I'd recommend you to change the &'a str into a String.


Addition:

I thought: Hey, so if deserializing would borrow from resp, we could just make resp live longer:

struct SomeStruct {
    resp: reqwest::Response,
}
impl SomeStruct {
    async fn new(url: &str) -> anyhow::Result<Self> {
        Ok(Self {
            resp: reqwest::get(url).await?,
        })
    }
    async fn snapshot<'a>(&'a self) -> anyhow::Result<Snapshot<'a>> {
        Ok(self.resp.json::<Snapshot<'a>>().await?)
    }
}

but no, that doesn't work. reqwest simply doesn't allow this kind of code:

pub async fn json<T: DeserializeOwned>(self) -> Result<T>

(DeserializeOwned says that T must be deserializable for any, arbitrary short lifetime of the callees picking.)

What you can do, however, is to not store resp, but its body.

struct SomeStruct {
    data: bytes::Bytes,
}
impl SomeStruct {
    async fn new(url: &str) -> anyhow::Result<Self> {
        Ok(Self {
            data: reqwest::get(url).await?.bytes().await?,
        })
    }
    async fn snapshot(&self) -> anyhow::Result<Snapshot<'_>> {
        Ok(serde_json::from_slice(&*self.data)?)
    }
}

I doubt this is advantageous for performance.

(Semi-related: I always worry about service denial attacks with this kind of code. What if somebody sends GBs of body?)

Related