Error trying to zero-copy deserialize using serde in Cloudflare workers

101 Views Asked by At

I have a struct containing &str with the same lifetime annotation as the struct itself. When I try to call the first::<T>(None) method on a worker::d1::D1PreparedStatement, which is supposed to query my database and parse the values into type T, I get "implementation of 'Deserialize' is not general enough".

Here some of my code:

use worker::;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct User<'a> {
    #[serde(with = "uuid_as_string")]
    uuid: Uuid,
    name: &'a str,
    display_name: &'a str,
    profile_text: &'a str,
    profile_picture_url: &'a str,
    email_address: &'a str,
    password: &'a str,
    created: usize,
    deleted: usize,
    timezone: isize,
}

mod uuid_as_string {
    use serde::{self, Deserialize, Deserializer, Serializer};
    use uuid::Uuid;

    pub fn serialize<S>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
    {
        serializer.serialize_str(&uuid.to_string())
    }

    pub fn deserialize<'a, D>(deserializer: D) -> Result<Uuid, D::Error>
        where
            D: Deserializer<'a>,
    {
        let s = String::deserialize(deserializer)?;
        Uuid::parse_str(&s).map_err(serde::de::Error::custom)
    }
}

#[event(fetch, respond_with_errors)]
pub async fn main(request: Request, env: Env, _ctx: Context) -> Result<Response> {
    Router::new()
        .getasync("/", |, ctx| async move {
            let d1 = ctx.env.d1("D1_DATABASE")?;
            let statement = d1.prepare("SELECT FROM users");
            match statement.first::<User>(None).await? {
                Some(user) => Response::from_json(&user),
                None => Response::error("Not found", 404),
            }
        })
        .run(request, env)
        .await
}

I tried completely removing the Uuid field and the module for handling it and it didn't change anything.

1

There are 1 best solutions below

0
On

The problem is that you can't do zero-copy deserialization in this case. The signature of workers::d1::D1PreparedStatement::first() is as follows:

pub async fn first<T>(&self, col_name: Option<&str>) -> Result<Option<T>>
where
    T: for<'a> Deserialize<'a>,
{
    // ...
}

That where T: for<'a> Deserialize<'a> means that the deserialization must be owned (note that this is exactly how DeserializeOwned is implemented), and therefore "can be deserialized without borrowing any data from the deserializer."

You must remove the borrowing from your struct to use it in this context, perhaps by replacing the &'a str fields with owned Strings.