diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 73aa4e2bb..0a0a2fc2e 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -1,8 +1,14 @@ -use crate::util::{escape_rust_keyword, field_not_ignored, trim_starting_raw_identifier}; +use crate::util::{ + escape_rust_keyword, field_not_ignored, format_field_ident, trim_starting_raw_identifier, +}; use heck::CamelCase; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned}; -use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Field, Fields, Lit, Meta, Type}; +use syn::{ + punctuated::{IntoIter, Punctuated}, + token::Comma, + Data, DataStruct, Field, Fields, Lit, Meta, Type, +}; /// Method to derive an [ActiveModel](sea_orm::ActiveModel) pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result { @@ -20,19 +26,19 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = all_fields - .clone() - .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string())) - .collect(); + Ok(quote!( + #derive_active_model + #derive_into_model + )) +} - let field: Vec = fields - .clone() - .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string())) - .collect(); +fn derive_active_model(all_fields: IntoIter) -> syn::Result { + let fields = all_fields.filter(field_not_ignored); + + let field: Vec = fields.clone().into_iter().map(format_field_ident).collect(); let name: Vec = fields .clone() @@ -71,27 +77,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields.into_iter().map(|Field { ty, .. }| ty).collect(); - let ignore_attr: Vec = all_fields - .clone() - .map(|field| !field_not_ignored(&field)) - .collect(); - - let field_value: Vec = all_field - .iter() - .zip(ignore_attr) - .map(|(field, ignore)| { - if ignore { - quote! { - Default::default() - } - } else { - quote! { - a.#field.into_value().unwrap().unwrap() - } - } - }) - .collect(); - Ok(quote!( #[derive(Clone, Debug, PartialEq)] pub struct ActiveModel { @@ -121,28 +106,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result for ::Model { - type Error = DbErr; - fn try_from(a: ActiveModel) -> Result { - #(if matches!(a.#field, sea_orm::ActiveValue::NotSet) { - return Err(DbErr::Custom(format!("field {} is NotSet", stringify!(#field)))); - })* - Ok( - Self { - #(#all_field: #field_value),* - } - ) - } - } - - #[automatically_derived] - impl sea_orm::TryIntoModel<::Model> for ActiveModel { - fn try_into_model(self) -> Result<::Model, DbErr> { - self.try_into() - } - } - #[automatically_derived] impl sea_orm::ActiveModelTrait for ActiveModel { type Entity = Entity; @@ -194,3 +157,62 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result) -> syn::Result { + let active_model_fields = model_fields.clone().filter(field_not_ignored); + + let active_model_field: Vec = active_model_fields + .into_iter() + .map(format_field_ident) + .collect(); + let model_field: Vec = model_fields + .clone() + .into_iter() + .map(format_field_ident) + .collect(); + + let ignore_attr: Vec = model_fields + .clone() + .map(|field| !field_not_ignored(&field)) + .collect(); + + let model_field_value: Vec = model_field + .iter() + .zip(ignore_attr) + .map(|(field, ignore)| { + if ignore { + quote! { + Default::default() + } + } else { + quote! { + a.#field.into_value().unwrap().unwrap() + } + } + }) + .collect(); + + Ok(quote!( + #[automatically_derived] + impl std::convert::TryFrom for ::Model { + type Error = DbErr; + fn try_from(a: ActiveModel) -> Result { + #(if matches!(a.#active_model_field, sea_orm::ActiveValue::NotSet) { + return Err(DbErr::Custom(format!("field {} is NotSet", stringify!(#active_model_field)))); + })* + Ok( + Self { + #(#model_field: #model_field_value),* + } + ) + } + } + + #[automatically_derived] + impl sea_orm::TryIntoModel<::Model> for ActiveModel { + fn try_into_model(self) -> Result<::Model, DbErr> { + self.try_into() + } + } + )) +} diff --git a/sea-orm-macros/src/util.rs b/sea-orm-macros/src/util.rs index 379b486ca..38b5a64c6 100644 --- a/sea-orm-macros/src/util.rs +++ b/sea-orm-macros/src/util.rs @@ -1,4 +1,5 @@ -use syn::{punctuated::Punctuated, token::Comma, Field, Meta}; +use quote::format_ident; +use syn::{punctuated::Punctuated, token::Comma, Field, Ident, Meta}; pub(crate) fn field_not_ignored(field: &Field) -> bool { for attr in field.attrs.iter() { @@ -25,6 +26,10 @@ pub(crate) fn field_not_ignored(field: &Field) -> bool { true } +pub(crate) fn format_field_ident(field: Field) -> Ident { + format_ident!("{}", field.ident.unwrap().to_string()) +} + pub(crate) fn trim_starting_raw_identifier(string: T) -> String where T: ToString, diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 266f907b6..9457591a8 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -37,8 +37,8 @@ pub use ActiveValue::NotSet; /// ``` #[derive(Clone, Debug)] pub enum ActiveValue - where - V: Into, +where + V: Into, { /// A defined [Value] actively being set Set(V), @@ -51,21 +51,21 @@ pub enum ActiveValue /// Defines a set operation on an [ActiveValue] #[allow(non_snake_case)] pub fn Set(v: V) -> ActiveValue - where - V: Into, +where + V: Into, { ActiveValue::set(v) } /// Defines an not set operation on an [ActiveValue] #[deprecated( -since = "0.5.0", -note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]" + since = "0.5.0", + note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]" )] #[allow(non_snake_case)] pub fn Unset(_: Option) -> ActiveValue - where - V: Into, +where + V: Into, { ActiveValue::not_set() } @@ -73,8 +73,8 @@ pub fn Unset(_: Option) -> ActiveValue /// Defines an unchanged operation on an [ActiveValue] #[allow(non_snake_case)] pub fn Unchanged(value: V) -> ActiveValue - where - V: Into, +where + V: Into, { ActiveValue::unchanged(value) } @@ -273,10 +273,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn insert<'a, C>(self, db: &'a C) -> Result<::Model, DbErr> - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_save(self, true)?; let model = ::insert(am) @@ -395,10 +395,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn update<'a, C>(self, db: &'a C) -> Result<::Model, DbErr> - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_save(self, false)?; let model: ::Model = Self::Entity::update(am).exec(db).await?; @@ -408,10 +408,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// Insert the model if primary key is `NotSet`, update otherwise. /// Only works if the entity has auto increment primary key. async fn save<'a, C>(self, db: &'a C) -> Result - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let mut is_update = true; for key in ::PrimaryKey::iter() { @@ -473,9 +473,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn delete<'a, C>(self, db: &'a C) -> Result - where - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_delete(self)?; let am_clone = am.clone(); @@ -489,9 +489,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// Note that this method will not alter the primary key values in ActiveModel. #[cfg(feature = "with-json")] fn set_from_json(&mut self, json: serde_json::Value) -> Result<(), DbErr> - where - <::Entity as EntityTrait>::Model: IntoActiveModel, - for<'de> <::Entity as EntityTrait>::Model: + where + <::Entity as EntityTrait>::Model: IntoActiveModel, + for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de>, { use crate::Iterable; @@ -519,9 +519,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// Create ActiveModel from a JSON value #[cfg(feature = "with-json")] fn from_json(json: serde_json::Value) -> Result - where - <::Entity as EntityTrait>::Model: IntoActiveModel, - for<'de> <::Entity as EntityTrait>::Model: + where + <::Entity as EntityTrait>::Model: IntoActiveModel, + for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de>, { use crate::{Iden, Iterable}; @@ -616,16 +616,16 @@ pub trait ActiveModelBehavior: ActiveModelTrait { /// A Trait for any type that can be converted into an ActiveModel pub trait IntoActiveModel - where - A: ActiveModelTrait, +where + A: ActiveModelTrait, { /// Method to call to perform the conversion fn into_active_model(self) -> A; } impl IntoActiveModel for A - where - A: ActiveModelTrait, +where + A: ActiveModelTrait, { fn into_active_model(self) -> A { self @@ -634,8 +634,8 @@ impl IntoActiveModel for A /// Constraints to perform the conversion of a type into an [ActiveValue] pub trait IntoActiveValue - where - V: Into, +where + V: Into, { /// Method to perform the conversion fn into_active_value(self) -> ActiveValue; @@ -720,8 +720,8 @@ impl_into_active_value!(crate::prelude::Decimal); impl_into_active_value!(crate::prelude::Uuid); impl Default for ActiveValue - where - V: Into, +where + V: Into, { /// Create an [ActiveValue::NotSet] fn default() -> Self { @@ -730,8 +730,8 @@ impl Default for ActiveValue } impl ActiveValue - where - V: Into, +where + V: Into, { /// Create an [ActiveValue::Set] pub fn set(value: V) -> Self { @@ -799,8 +799,8 @@ impl ActiveValue } impl std::convert::AsRef for ActiveValue - where - V: Into, +where + V: Into, { fn as_ref(&self) -> &V { match self { @@ -811,8 +811,8 @@ impl std::convert::AsRef for ActiveValue } impl PartialEq for ActiveValue - where - V: Into + std::cmp::PartialEq, +where + V: Into + std::cmp::PartialEq, { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -825,8 +825,8 @@ impl PartialEq for ActiveValue } impl From> for ActiveValue> - where - V: Into + Nullable, +where + V: Into + Nullable, { fn from(value: ActiveValue) -> Self { match value { @@ -867,7 +867,7 @@ mod tests { name: "Apple".to_owned(), cake_id: 1, } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: Set("Apple".to_owned()), @@ -894,7 +894,7 @@ mod tests { my_fruit::UpdateFruit { cake_id: Some(Some(1)), } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, @@ -906,7 +906,7 @@ mod tests { my_fruit::UpdateFruit { cake_id: Some(None), } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, @@ -950,40 +950,50 @@ mod tests { id: Set(1), name: Set("Pineapple".to_owned()), cake_id: Set(None), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 1, name: "Pineapple".to_owned(), cake_id: None, - }); + } + ); assert_eq!( my_fruit::ActiveModel { id: Set(2), name: Set("Apple".to_owned()), cake_id: Set(Some(1)), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 2, name: "Apple".to_owned(), cake_id: Some(1), - }); + } + ); assert_eq!( my_fruit::ActiveModel { id: Set(1), name: NotSet, cake_id: Set(None), - }.try_into_model(), - Err(DbErr::Custom(String::from("field name is NotSet")))); + } + .try_into_model(), + Err(DbErr::Custom(String::from("field name is NotSet"))) + ); assert_eq!( my_fruit::ActiveModel { id: Set(1), name: Set("Pineapple".to_owned()), cake_id: NotSet, - }.try_into_model(), - Err(DbErr::Custom(String::from("field cake_id is NotSet")))); + } + .try_into_model(), + Err(DbErr::Custom(String::from("field cake_id is NotSet"))) + ); } #[test] @@ -1012,12 +1022,15 @@ mod tests { my_fruit::ActiveModel { id: Set(1), name: Set("Pineapple".to_owned()), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 1, name: "Pineapple".to_owned(), cake_id: None, - }); + } + ); } #[test] @@ -1046,18 +1059,21 @@ mod tests { my_fruit::ActiveModel { id: Set(1), cake_id: Set(Some(1)), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 1, name: "".to_owned(), cake_id: Some(1), - }); + } + ); } #[test] #[cfg(feature = "with-json")] #[should_panic( - expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"# + expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"# )] fn test_active_model_set_from_json_1() { let mut cake: cake::ActiveModel = Default::default(); @@ -1065,7 +1081,7 @@ mod tests { cake.set_from_json(json!({ "name": "Apple Pie", })) - .unwrap(); + .unwrap(); } #[test]