diff --git a/sea-orm-macros/src/derives/model_ex.rs b/sea-orm-macros/src/derives/model_ex.rs index 77b3adb52..ed3d81b89 100644 --- a/sea-orm-macros/src/derives/model_ex.rs +++ b/sea-orm-macros/src/derives/model_ex.rs @@ -13,12 +13,63 @@ use syn::{ pub fn expand_sea_orm_model(input: ItemStruct, compact: bool) -> syn::Result { let model = input.ident; - let model_attrs = input.attrs; let vis = input.vis; let mut all_fields = input.fields; + let mut model_attrs: Vec = Vec::new(); + let mut model_ex_attrs: Vec = Vec::new(); + + for attr in input.attrs { + if !attr.path().is_ident("sea_orm") { + model_attrs.push(attr.clone()); + model_ex_attrs.push(attr); + continue; + } + + let mut other_attrs = Punctuated::::new(); + + attr.parse_nested_meta(|meta| { + let is_model = meta.path.is_ident("model_attrs"); + let is_model_ex = meta.path.is_ident("model_ex_attrs"); + + if is_model || is_model_ex { + let content; + syn::parenthesized!(content in meta.input); + use syn::parse::Parse; + let nested_metas = content.parse_terminated(Meta::parse, Comma)?; + for m in nested_metas { + let new_attr: Attribute = parse_quote!( #[#m] ); + if is_model { + model_attrs.push(new_attr); + } else { + model_ex_attrs.push(new_attr); + } + } + } else { + let path = &meta.path; + if meta.input.peek(syn::Token![=]) { + let value: Expr = meta.value()?.parse()?; + other_attrs.push(parse_quote!( #path = #value )); + } else if meta.input.is_empty() || meta.input.peek(Comma) { + other_attrs.push(parse_quote!( #path )); + } else { + let content; + syn::parenthesized!(content in meta.input); + let tokens: TokenStream = content.parse()?; + other_attrs.push(parse_quote!( #path(#tokens) )); + } + } + Ok(()) + })?; + + if !other_attrs.is_empty() { + let attr: Attribute = parse_quote!( #[sea_orm(#other_attrs)] ); + model_attrs.push(attr.clone()); + model_ex_attrs.push(attr); + } + } + let model_ex = Ident::new(&format!("{model}Ex"), model.span()); - let mut model_ex_attrs = model_attrs.clone(); for attr in &mut model_ex_attrs { if attr.path().is_ident("derive") { if let Meta::List(list) = &mut attr.meta { diff --git a/tests/derive_model_tests.rs b/tests/derive_model_tests.rs new file mode 100644 index 000000000..e4eb6cc04 --- /dev/null +++ b/tests/derive_model_tests.rs @@ -0,0 +1,76 @@ +use sea_orm::prelude::{HasMany, HasOne}; + +mod cake { + use sea_orm::prelude::*; + use serde::Serialize; + + #[sea_orm::model] + #[derive(DeriveEntityModel, Debug, Clone, Serialize)] + #[sea_orm(table_name = "cake")] + #[sea_orm(model_attrs(serde(rename_all = "UPPERCASE")))] + #[sea_orm(model_ex_attrs(serde(rename_all = "PascalCase")))] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(has_many)] + pub fruits: HasMany, + } + + impl ActiveModelBehavior for ActiveModel {} +} + +mod fruit { + use sea_orm::prelude::*; + use serde::Serialize; + + #[sea_orm::model] + #[derive(DeriveEntityModel, Debug, Clone)] + #[sea_orm( + table_name = "fruit", + model_attrs(derive(Serialize), serde(rename_all = "UPPERCASE")), + model_ex_attrs(derive(Serialize), serde(rename_all = "PascalCase")) + )] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub cake_id: Option, + #[sea_orm(belongs_to, from = "cake_id", to = "id")] + pub cake: HasOne, + } + + impl ActiveModelBehavior for ActiveModel {} +} + +#[test] +fn main() -> Result<(), serde_json::Error> { + use sea_orm::EntityName; + assert_eq!(cake::Entity.table_name(), "cake"); + assert_eq!(fruit::Entity.table_name(), "fruit"); + + assert_eq!(serde_json::to_string(&cake::Model { id: 1 })?, "{\"ID\":1}"); + assert_eq!( + serde_json::to_string(&cake::ModelEx { + id: 1, + fruits: HasMany::Loaded(Vec::new()), + })?, + "{\"Id\":1,\"Fruits\":[]}" + ); + + assert_eq!( + serde_json::to_string(&fruit::Model { + id: 2, + cake_id: Some(1) + })?, + "{\"ID\":2,\"CAKE_ID\":1}" + ); + assert_eq!( + serde_json::to_string(&fruit::ModelEx { + id: 2, + cake_id: Some(1), + cake: HasOne::Unloaded, + })?, + "{\"Id\":2,\"CakeId\":1,\"Cake\":null}" + ); + + Ok(()) +}