sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

Deriving FromRow for structs with third-party types

Open deklanw opened this issue 5 years ago • 4 comments

Opening an issue for this as discussed on Discord.

#[derive(FromRow)]
struct CustomType {
   a: String,
   b: i32,
   third_party: ThirdParty,
}

It would be convenient if there were a way to make this work like with serde's with field attributes: https://serde.rs/field-attrs.html#serialize_with

In my case ThirdParty implements from_str

Until this is possible, I've gone ahead and manually implemented FromRow for my situation

deklanw avatar Jun 13 '20 19:06 deklanw

A related thing to add that I'm facing is to derive FromRow on an imported struct.

use some_create::ThirdParty;
#[derive(FromRow)] // for ThirdParty;

I'm looking for a reasonable solution but can't find it yet.

alun avatar Aug 08 '20 14:08 alun

I just stumbled upon the same issue and found this ticket. It would be really nice if we could do it.

A related thing to add that I'm facing is to derive FromRow on an imported struct.

That is unfortunately not possible in Rust because of its orphan rules.

depressed-pho avatar Feb 28 '24 07:02 depressed-pho

+1'ing this, I'd like to implement Type and sqlx(transparent) for some third-party type. For now, I'm using a workaround with try_from:

#[derive(sqlx::Type)]
#[sqlx(transparent, no_pg_array)]
struct UserIdDbParser(i64);

impl From<UserIdDbParser> for UserId {
    fn from(value: UserIdDbParser) -> Self {
        Self(value.0 as u64)
    }
}

// in my structs:

#[derive(Clone, sqlx::FromRow)]
pub struct User {
    #[sqlx(try_from = "UserIdDbParser")]
    pub id: UserId,
}

taminomara avatar May 17 '24 19:05 taminomara

Here's a macro I came up with for defining the above parser quickly, and some usage examples:

macro_rules! impl_db_parser {
    {
        $( $name:ident ( $db_ty:ty ) => $ty:ty, $conv:expr $(,)? );+ $(;)?
    } => {
        mod convert {
            $( impl_db_parser!(@impl $name ($db_ty) => $ty, $conv); )+
        }
        mod convert_opt {
            $( impl_db_parser!(@impl @opt $name ($db_ty) => $ty, $conv); )+
        }
    };
    (@impl $name:ident ( $db_ty:ty ) => $ty:ty, $conv:expr) => {
        #[derive(sqlx::Type)]
        #[sqlx(transparent)]
        pub struct $name($db_ty);

        impl From<$name> for $ty {
            fn from(value: $name) -> Self {
                $conv(value.0)
            }
        }
    };
    (@impl @opt $name:ident ( $db_ty:ty ) => $ty:ty, $conv:expr) => {
        #[derive(sqlx::Type)]
        #[sqlx(transparent)]
        pub struct $name(Option<$db_ty>);

        impl From<$name> for Option<$ty> {
            fn from(value: $name) -> Self {
                value.0.map($conv)
            }
        }
    }
}

impl_db_parser!{
    U8(i8) => u8, |x| x as u8;
    U16(i16) => u16, |x| x as u16;
    U32(i32) => u32, |x| x as u32;
    U64(i64) => u64, |x| x as u64;
    UserId(i64) => teloxide::types::UserId, |x| teloxide::types::UserId(x as u64);
    ChatId(i64) => teloxide::types::ChatId, teloxide::types::ChatId;
}

taminomara avatar May 17 '24 20:05 taminomara