benzina 0.5.1

Various helper types and macros for `diesel`
Documentation
use std::fmt::Debug;

use diesel::{
    deserialize::{FromSql, FromSqlRow},
    expression::AsExpression,
    pg::{Pg, PgValue},
    serialize::{IsNull, ToSql},
    sql_types::Nullable,
};
use serde_core::{Serialize, de::DeserializeOwned};

macro_rules! impl_nullable {
    (
        $($type:ident => $diesel_type:ident => $uppercase_diesel_type:ident => $serializer:path => $deserializer:path => $diesel_type_import:path),*
    ) => {
        $(
            #[doc = concat!("A diesel [`Nullable`]<[`", stringify!($diesel_type), "`]> serialization and")]
            #[doc = "deserialization wrapper"]
            #[doc = ""]
            #[doc = concat!("Diesel only implements [`FromSql`] and [`ToSql`] for `Option`<[`serde_json::Value`]>, making")]
            #[doc = concat!("it hard to deal with _nullable_ `", stringify!($uppercase_diesel_type), "` columns. This type implements [`FromSql`] and [`ToSql`]")]
            #[doc = "for any type that implements [`Deserialize`] and [`Serialize`] respectively."]
            #[doc = ""]
            #[doc = "This type is not intended to be used directly in the model but rather to be used with diesel [`serialize_as`] and [`deserialize_as`]."]
            #[doc = "```"]
            #[doc = concat!("use benzina::{", stringify!($type), ", U31};")]
            #[doc = "use diesel::{Queryable, Insertable, sql_types::Nullable};"]
            #[doc = "use serde::{Deserialize, Serialize};"]
            #[doc = ""]
            #[doc = "#[derive(Debug, Queryable)]"]
            #[doc = "#[diesel(table_name = users, check_for_backend(diesel::pg::Pg))]"]
            #[doc = "struct User {"]
            #[doc = "    id: U31,"]
            #[doc = "    first_name: String,"]
            #[doc = "    last_name: String,"]
            #[doc = concat!("    #[diesel(deserialize_as = ", stringify!($type), "<UserPermissions>)]")]
            #[doc = "    permissions: Option<UserPermissions>,"]
            #[doc = "}"]
            #[doc = ""]
            #[doc = "#[derive(Debug, Insertable)]"]
            #[doc = "#[diesel(table_name = users)]"]
            #[doc = "struct NewUser {"]
            #[doc = "    first_name: String,"]
            #[doc = "    last_name: String,"]
            #[doc = concat!("    #[diesel(serialize_as = ", stringify!($type),"<UserPermissions>)]")]
            #[doc = "    permissions: Option<UserPermissions>,"]
            #[doc = "}"]
            #[doc = ""]
            #[doc = "#[derive(Debug, Serialize, Deserialize)]"]
            #[doc = "struct UserPermissions {"]
            #[doc = "    can_delete: bool,"]
            #[doc = "    can_update: bool,"]
            #[doc = "    can_read: bool,"]
            #[doc = "}"]
            #[doc = ""]
            #[doc = "diesel::table! {"]
            #[doc = "    users (id) {"]
            #[doc = "        id -> Int4,"]
            #[doc = "        first_name -> Text,"]
            #[doc = "        last_name -> Text,"]
            #[doc = concat!("        permissions -> Nullable<", stringify!($diesel_type), ">,")]
            #[doc = "    }"]
            #[doc = "}"]
            #[doc = "```"]
            #[doc = "[`Nullable`]: diesel::sql_types::Nullable"]
            #[doc = concat!("[`", stringify!($diesel_type), "`]: diesel::sql_types::", stringify!($diesel_type))]
            #[doc = "[`FromSql`]: diesel::deserialize::FromSql"]
            #[doc = "[`ToSql`]: diesel::serialize::ToSql"]
            #[doc = "[`serde_json::Value`]: serde_json::Value"]
            #[doc = "[`Serialize`]: serde_core::Serialize"]
            #[doc = "[`Deserialize`]: serde_core::Deserialize"]
            #[doc = "[`serialize_as`]: diesel::prelude::Insertable#optional-field-attributes"]
            #[doc = "[`deserialize_as`]: diesel::prelude::Queryable#deserialize_as-attribute"]
            #[derive(
                Debug,
                Default,
                Clone,
                Copy,
                PartialEq,
                Eq,
                PartialOrd,
                Ord,
                Hash,
                FromSqlRow,
                AsExpression,
            )]
            #[diesel(sql_type = Nullable<$diesel_type_import>)]
            pub struct $type<T: Sized>(Option<T>);

            impl<T> $type<T> {
                pub const fn new(value: Option<T>) -> Self {
                    Self(value)
                }

                pub fn get(&self) -> Option<&T> {
                    self.0.as_ref()
                }

                pub fn into_inner(self) -> Option<T> {
                    self.0
                }
            }

            impl<T> From<$type<T>> for Option<T> {
                fn from(value: $type<T>) -> Self {
                    value.into_inner()
                }
            }

            impl<T> From<Option<T>> for $type<T> {
                fn from(value: Option<T>) -> Self {
                    Self::new(value)
                }
            }

            impl<T> FromSql<Nullable<$diesel_type_import>, Pg> for $type<T>
            where
                T: DeserializeOwned,
            {
                fn from_sql(value: PgValue) -> diesel::deserialize::Result<Self> {
                    $deserializer(value).map(Self)
                }

                fn from_nullable_sql(value: Option<PgValue>) -> diesel::deserialize::Result<Self> {
                    Ok(match value {
                        Some(bytes) => Self::from_sql(bytes)?,
                        None => Self(None),
                    })
                }
            }

            impl<T> ToSql<Nullable<$diesel_type_import>, Pg> for $type<T>
            where
                T: Debug + Serialize,
            {
                fn to_sql(&self, out: &mut diesel::serialize::Output<Pg>) -> diesel::serialize::Result {
                    if let Some(value) = &self.0 {
                        $serializer(value, out)
                    } else {
                        Ok(IsNull::Yes)
                    }
                }
            }
        )*
    };
}

impl_nullable!(
    NullableJson => Json => JSON => crate::json::convert::sql_serialize => crate::json::convert::sql_deserialize => diesel::sql_types::Json,
    NullableJsonb => Jsonb => JSONB => crate::json::convert::sql_serialize_binary => crate::json::convert::sql_deserialize_binary => diesel::pg::sql_types::Jsonb
);