diff --git a/sea-orm-macros/src/derives/into_active_model.rs b/sea-orm-macros/src/derives/into_active_model.rs new file mode 100644 index 00000000..d40a0a4b --- /dev/null +++ b/sea-orm-macros/src/derives/into_active_model.rs @@ -0,0 +1,101 @@ +use bae::FromAttributes; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned}; + +#[derive(Default, FromAttributes)] +pub struct SeaOrm { + pub active_model: Option, +} + +enum Error { + InputNotStruct, + Syn(syn::Error), +} + +struct IntoActiveModel { + attrs: SeaOrm, + fields: syn::punctuated::Punctuated, + field_idents: Vec, + ident: syn::Ident, +} + +impl IntoActiveModel { + fn new(input: syn::DeriveInput) -> Result { + let fields = match input.data { + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(syn::FieldsNamed { named, .. }), + .. + }) => named, + _ => return Err(Error::InputNotStruct), + }; + + let attrs = SeaOrm::try_from_attributes(&input.attrs) + .map_err(Error::Syn)? + .unwrap_or_default(); + + let ident = input.ident; + + let field_idents = fields + .iter() + .map(|field| field.ident.as_ref().unwrap().clone()) + .collect(); + + Ok(IntoActiveModel { + attrs, + fields, + field_idents, + ident, + }) + } + + fn expand(&self) -> syn::Result { + let expanded_impl_into_active_model = self.impl_into_active_model(); + + Ok(expanded_impl_into_active_model) + } + + fn impl_into_active_model(&self) -> TokenStream { + let Self { + attrs, + ident, + field_idents, + fields, + } = self; + + let active_model_ident = attrs + .active_model + .clone() + .unwrap_or_else(|| syn::Ident::new("ActiveModel", Span::call_site())); + + let expanded_fields_into_active_model = fields.iter().map(|field| { + let field_ident = field.ident.as_ref().unwrap(); + + quote!( + ::sea_orm::IntoActiveValue::<_>::into_active_value(self.#field_ident).into() + ) + }); + + quote!( + impl ::sea_orm::IntoActiveModel<#active_model_ident> for #ident { + fn into_active_model(self) -> #active_model_ident { + #active_model_ident { + #( #field_idents: #expanded_fields_into_active_model, )* + ..::std::default::Default::default() + } + } + } + ) + } +} + +pub fn expand_into_active_model(input: syn::DeriveInput) -> syn::Result { + let ident_span = input.ident.span(); + + match IntoActiveModel::new(input) { + Ok(model) => model.expand(), + Err(Error::InputNotStruct) => Ok(quote_spanned! { + ident_span => compile_error!("you can only derive IntoActiveModel on structs"); + }), + Err(Error::Syn(err)) => Err(err), + } +} diff --git a/sea-orm-macros/src/derives/mod.rs b/sea-orm-macros/src/derives/mod.rs index 8dfb7ac1..6ba19a92 100644 --- a/sea-orm-macros/src/derives/mod.rs +++ b/sea-orm-macros/src/derives/mod.rs @@ -4,6 +4,7 @@ mod column; mod entity; mod entity_model; mod from_query_result; +mod into_active_model; mod model; mod primary_key; mod relation; @@ -14,6 +15,7 @@ pub use column::*; pub use entity::*; pub use entity_model::*; pub use from_query_result::*; +pub use into_active_model::*; pub use model::*; pub use primary_key::*; pub use relation::*; diff --git a/sea-orm-macros/src/lib.rs b/sea-orm-macros/src/lib.rs index c3f8a50f..cf8c2f3c 100644 --- a/sea-orm-macros/src/lib.rs +++ b/sea-orm-macros/src/lib.rs @@ -84,6 +84,14 @@ pub fn derive_active_model(input: TokenStream) -> TokenStream { } } +#[proc_macro_derive(DeriveIntoActiveModel, attributes(sea_orm))] +pub fn derive_into_active_model(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + derives::expand_into_active_model(input) + .unwrap_or_else(Error::into_compile_error) + .into() +} + #[proc_macro_derive(DeriveActiveModelBehavior)] pub fn derive_active_model_behavior(input: TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 4692bce6..a2b22359 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -2,7 +2,7 @@ use crate::{ error::*, ConnectionTrait, DeleteResult, EntityTrait, Iterable, PrimaryKeyToColumn, Value, }; use async_trait::async_trait; -use sea_query::ValueTuple; +use sea_query::{Nullable, ValueTuple}; use std::fmt::Debug; #[derive(Clone, Debug, Default)] @@ -214,6 +214,83 @@ where } } +pub trait IntoActiveValue +where + V: Into, +{ + fn into_active_value(self) -> ActiveValue; +} + +macro_rules! impl_into_active_value { + ($ty: ty, $fn: ident) => { + impl IntoActiveValue<$ty> for $ty { + fn into_active_value(self) -> ActiveValue<$ty> { + $fn(self) + } + } + + impl IntoActiveValue> for Option<$ty> { + fn into_active_value(self) -> ActiveValue> { + match self { + Some(value) => Set(Some(value)), + None => Unset(None), + } + } + } + + impl IntoActiveValue> for Option> { + fn into_active_value(self) -> ActiveValue> { + match self { + Some(value) => Set(value), + None => Unset(None), + } + } + } + }; +} + +impl_into_active_value!(bool, Set); +impl_into_active_value!(i8, Set); +impl_into_active_value!(i16, Set); +impl_into_active_value!(i32, Set); +impl_into_active_value!(i64, Set); +impl_into_active_value!(u8, Set); +impl_into_active_value!(u16, Set); +impl_into_active_value!(u32, Set); +impl_into_active_value!(u64, Set); +impl_into_active_value!(f32, Set); +impl_into_active_value!(f64, Set); +impl_into_active_value!(&'static str, Set); +impl_into_active_value!(String, Set); + +#[cfg(feature = "with-json")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-json")))] +impl_into_active_value!(crate::prelude::Json, Set); + +#[cfg(feature = "with-chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] +impl_into_active_value!(crate::prelude::Date, Set); + +#[cfg(feature = "with-chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] +impl_into_active_value!(crate::prelude::Time, Set); + +#[cfg(feature = "with-chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] +impl_into_active_value!(crate::prelude::DateTime, Set); + +#[cfg(feature = "with-chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] +impl_into_active_value!(crate::prelude::DateTimeWithTimeZone, Set); + +#[cfg(feature = "with-rust_decimal")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-rust_decimal")))] +impl_into_active_value!(crate::prelude::Decimal, Set); + +#[cfg(feature = "with-uuid")] +#[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] +impl_into_active_value!(crate::prelude::Uuid, Set); + impl ActiveValue where V: Into, @@ -290,3 +367,16 @@ where self.value.as_ref() == other.value.as_ref() } } + +impl From> for ActiveValue> +where + V: Into + Nullable, +{ + fn from(value: ActiveValue) -> Self { + match value.state { + ActiveValueState::Set => Set(value.value), + ActiveValueState::Unset => Unset(None), + ActiveValueState::Unchanged => ActiveValue::unchanged(value.value), + } + } +} diff --git a/src/entity/prelude.rs b/src/entity/prelude.rs index 47ce7053..efaab639 100644 --- a/src/entity/prelude.rs +++ b/src/entity/prelude.rs @@ -8,7 +8,7 @@ pub use crate::{ #[cfg(feature = "macros")] pub use crate::{ DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, + DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, DeriveIntoActiveModel }; #[cfg(feature = "with-json")] diff --git a/src/lib.rs b/src/lib.rs index 1b78cf58..c66cb3f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -287,7 +287,7 @@ pub use schema::*; #[cfg(feature = "macros")] pub use sea_orm_macros::{ DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn, DeriveCustomColumn, DeriveEntity, - DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, FromQueryResult, + DeriveEntityModel, DeriveModel, DerivePrimaryKey, DeriveRelation, FromQueryResult, DeriveIntoActiveModel, }; pub use sea_query;