@@ -9,13 +9,48 @@ enum Error {
99 Syn ( syn:: Error ) ,
1010}
1111
12+ /// Matches all potential ways to convert struct fields into ActiveModel ones
13+ pub ( super ) enum IntoActiveModelField {
14+ /// `IntoActiveValue::into_active_value(self.field).into()`
15+ Normal ( syn:: Ident ) ,
16+ /// Option<T> with fallback: `Some(v) => Set(v).into(), None => Set(expr).into()`
17+ WithDefault { ident : syn:: Ident , expr : syn:: Expr } ,
18+ }
19+
20+ impl IntoActiveModelField {
21+ pub ( super ) fn ident ( & self ) -> & syn:: Ident {
22+ match self {
23+ IntoActiveModelField :: Normal ( ident) => ident,
24+ IntoActiveModelField :: WithDefault { ident, .. } => ident,
25+ }
26+ }
27+ }
28+
29+ /// Contains all the information extracted from the input struct and its attributes
30+ /// needed to generate the `IntoActiveModel` trait implementation.
1231pub ( super ) struct DeriveIntoActiveModel {
32+ /// The identifier of the input struct
1333 pub ident : syn:: Ident ,
34+ /// Optional explicit ActiveModel type specified via `#[sea_orm(active_model = "Type")]`
1435 pub active_model : Option < syn:: Type > ,
15- pub fields : Vec < syn:: Ident > ,
36+ /// handles provided struct fields
37+ pub fields : Vec < IntoActiveModelField > ,
38+ /// handles fields set by #[sea_orm(set(field = expr))]
39+ pub set_fields : Vec < ( syn:: Ident , syn:: Expr ) > ,
40+ /// require all fields specified, no `..default::Default()`
41+ pub exhaustive : bool ,
1642}
1743
1844impl DeriveIntoActiveModel {
45+ /// This function finds attributes relevant for this macros:
46+ /// Container attributes (#[sea_orm(...)]) on the struct for:
47+ /// - active_model: explicit ActiveModel type
48+ /// - exhaustive: require all fields to be set
49+ /// - set/fill(...): provided values for ommited fields
50+ ///
51+ /// Field attributes (#[sea_orm(...)]) with:
52+ /// - ignore/skip: exclude from conversion
53+ /// - default: fallback value for Option<T> fields
1954 fn new ( input : syn:: DeriveInput ) -> Result < Self , Error > {
2055 let fields = match input. data {
2156 syn:: Data :: Struct ( syn:: DataStruct {
@@ -26,30 +61,65 @@ impl DeriveIntoActiveModel {
2661 } ;
2762
2863 let mut active_model = None ;
64+ let mut set_fields = Vec :: new ( ) ;
65+ let mut exhaustive = false ;
2966
3067 for attr in input. attrs . iter ( ) {
3168 if !attr. path ( ) . is_ident ( "sea_orm" ) {
3269 continue ;
3370 }
3471
72+ // Parse container attributes: #[sea_orm(...)]
73+ // Supports:
74+ // - active_model = "Type": explicitly specify the ActiveModel type
75+ // - exhaustive: require all ActiveModel fields to be explicitly set
76+ // - set(field = expr, ...): provide default values for fields not in the input struct
3577 if let Ok ( list) = attr. parse_args_with ( Punctuated :: < Meta , Comma > :: parse_terminated) {
3678 for meta in list {
79+ // Parse active_model attribute: #[sea_orm(active_model = "MyActiveModel")]
3780 if let Some ( s) = meta. get_as_kv ( "active_model" ) {
3881 active_model = Some ( syn:: parse_str :: < syn:: Type > ( & s) . map_err ( Error :: Syn ) ?) ;
3982 }
83+ // Parse exhaustive flag: #[sea_orm(exhaustive)]
84+ // When set, prevents using Default::default() for unspecified fields
85+ if meta. exists ( "exhaustive" ) {
86+ exhaustive = true ;
87+ }
88+ // Parse set/fill attribute: #[sea_orm(set(field1 = expr1, field2 = expr2, ...))]
89+ // Collects field assignments to be included in the generated ActiveModel
90+ if let Meta :: List ( meta_list) = & meta {
91+ if meta_list. path . is_ident ( "set" ) || meta_list. path . is_ident ( "fill" ) {
92+ let nested = meta_list
93+ . parse_args_with ( Punctuated :: < Meta , Comma > :: parse_terminated)
94+ . map_err ( Error :: Syn ) ?;
95+ for nested_meta in nested {
96+ if let Some ( val) = nested_meta. get_as_kv_with_ident ( ) {
97+ let ( ident, expr_str) = val;
98+ let expr = syn:: parse_str :: < syn:: Expr > ( & expr_str)
99+ . map_err ( Error :: Syn ) ?;
100+ set_fields. push ( ( ident, expr) ) ;
101+ }
102+ }
103+ }
104+ }
40105 }
41106 }
42107 }
43108
44- let field_idents = fields
45- . iter ( )
46- . map ( |field| field. ident . as_ref ( ) . unwrap ( ) . clone ( ) )
47- . collect ( ) ;
109+ // Field attributes
110+ let mut field_idents: Vec < IntoActiveModelField > = Vec :: new ( ) ;
111+ for field in fields. iter ( ) {
112+ if let Some ( f) = parse_field ( field) ? {
113+ field_idents. push ( f) ;
114+ }
115+ }
48116
49117 Ok ( Self {
50118 ident : input. ident ,
51119 active_model,
52120 fields : field_idents,
121+ set_fields,
122+ exhaustive,
53123 } )
54124 }
55125
@@ -59,17 +129,21 @@ impl DeriveIntoActiveModel {
59129 Ok ( expanded_impl_into_active_model)
60130 }
61131
132+ /// Generates the implementation of `IntoActiveModel` trait for the input struct
62133 pub ( super ) fn impl_into_active_model ( & self ) -> TokenStream {
63134 let Self {
64135 ident,
65136 active_model,
66137 fields,
138+ set_fields,
139+ exhaustive,
67140 } = self ;
68141
69142 let mut active_model_ident = active_model
70143 . clone ( )
71144 . unwrap_or_else ( || syn:: parse_str :: < syn:: Type > ( "ActiveModel" ) . unwrap ( ) ) ;
72145
146+ // Create a type alias for qualified types
73147 let type_alias_definition = if is_qualified_type ( & active_model_ident) {
74148 let type_alias = format_ident ! ( "ActiveModelFor{ident}" ) ;
75149 let type_def = quote ! ( type #type_alias = #active_model_ident; ) ;
@@ -90,28 +164,111 @@ impl DeriveIntoActiveModel {
90164 quote ! ( )
91165 } ;
92166
93- let expanded_fields = fields. iter ( ) . map ( |field_ident| {
167+ let field_idents: Vec < _ > = fields. iter ( ) . map ( |f| f. ident ( ) ) . collect ( ) ;
168+
169+ // Generate field conversion code based on field type
170+ let expanded_fields = fields. iter ( ) . map ( |field| match field {
171+ IntoActiveModelField :: Normal ( ident) => quote ! (
172+ sea_orm:: IntoActiveValue :: <_>:: into_active_value( self . #ident) . into( )
173+ ) ,
174+ IntoActiveModelField :: WithDefault { ident, expr } => quote ! ( {
175+ match self . #ident. into( ) {
176+ Some ( v) => sea_orm:: ActiveValue :: Set ( v) . into( ) ,
177+ None => sea_orm:: ActiveValue :: Set ( #expr) . into( ) ,
178+ }
179+ } ) ,
180+ } ) ;
181+
182+ // Add custom field assignments from #[sea_orm(set(field = expr))]
183+ let ( set_idents, set_exprs) : ( Vec < _ > , Vec < _ > ) = set_fields. iter ( ) . cloned ( ) . unzip ( ) ;
184+ let expanded_sets = set_exprs. iter ( ) . map ( |expr| {
94185 quote ! (
95- sea_orm:: IntoActiveValue :: <_> :: into_active_value ( self . #field_ident ) . into ( )
186+ sea_orm:: ActiveValue :: Set ( #expr )
96187 )
97188 } ) ;
98189
190+ // Add defaults(Unset) unless exhaustive mode is enabled
191+ let rest = if * exhaustive {
192+ quote ! ( )
193+ } else {
194+ quote ! ( ..:: std:: default :: Default :: default ( ) )
195+ } ;
196+
99197 quote ! (
100198 #type_alias_definition
101199
102200 #[ automatically_derived]
103201 impl sea_orm:: IntoActiveModel <#active_model_ident> for #ident {
104202 fn into_active_model( self ) -> #active_model_ident {
105203 #active_model_ident {
106- #( #fields: #expanded_fields, ) *
107- ..:: std:: default :: Default :: default ( )
204+ #( #field_idents: #expanded_fields, ) *
205+ #( #set_idents: #expanded_sets, ) *
206+ #rest
108207 }
109208 }
110209 }
111210 )
112211 }
113212}
114213
214+ /// Parse field-level attributes on each struct field
215+ /// Supports:
216+ /// - ignore or skip: exclude the field from conversion
217+ /// - default = "expr": provide a fallback value for Option<T> fields (Some(v) => Set(v), None => Set(expr))
218+ fn parse_field ( field : & syn:: Field ) -> Result < Option < IntoActiveModelField > , Error > {
219+ let ident = field. ident . as_ref ( ) . unwrap ( ) . clone ( ) ;
220+ // Default expression for this field
221+ let mut default_expr: Option < syn:: Expr > = None ;
222+
223+ for attr in field. attrs . iter ( ) {
224+ if !attr. path ( ) . is_ident ( "sea_orm" ) {
225+ continue ;
226+ }
227+
228+ // Parse the attribute arguments: #[sea_orm(...)]
229+ if let Ok ( list) = attr. parse_args_with ( Punctuated :: < Meta , Comma > :: parse_terminated) {
230+ for meta in list. iter ( ) {
231+ // Check for ignore/skip: #[sea_orm(ignore)] or #[sea_orm(skip)]
232+ if meta. exists ( "ignore" ) || meta. exists ( "skip" ) {
233+ return Ok ( None ) ;
234+ }
235+ // Check for bare default: #[sea_orm(default)]
236+ if meta. exists ( "default" ) {
237+ if default_expr. is_some ( ) {
238+ return Err ( Error :: Syn ( syn:: Error :: new_spanned (
239+ meta,
240+ "duplicate `default` attribute" ,
241+ ) ) ) ;
242+ }
243+ let expr: syn:: Expr = syn:: parse_quote!( :: core:: default :: Default :: default ( ) ) ;
244+ default_expr = Some ( expr) ;
245+ continue ; // Skip next default check
246+ }
247+ // Check for default value: #[sea_orm(default = "expr")]
248+ if let Some ( expr_str) = meta. get_as_kv ( "default" ) {
249+ // Error on duplicate `default`
250+ if default_expr. is_some ( ) {
251+ return Err ( Error :: Syn ( syn:: Error :: new_spanned (
252+ meta,
253+ "duplicate `default` attribute" ,
254+ ) ) ) ;
255+ }
256+ // Parse the expression string into a syn::Expr
257+ let expr = syn:: parse_str :: < syn:: Expr > ( & expr_str) . map_err ( Error :: Syn ) ?;
258+ default_expr = Some ( expr) ;
259+ }
260+ }
261+ }
262+ }
263+
264+ // Finnaly match and return appropriate field type
265+ if let Some ( expr) = default_expr {
266+ Ok ( Some ( IntoActiveModelField :: WithDefault { ident, expr } ) )
267+ } else {
268+ Ok ( Some ( IntoActiveModelField :: Normal ( ident) ) )
269+ }
270+ }
271+
115272/// Method to derive the ActiveModel from the [ActiveModelTrait](sea_orm::ActiveModelTrait)
116273pub fn expand_into_active_model ( input : syn:: DeriveInput ) -> syn:: Result < TokenStream > {
117274 let ident_span = input. ident . span ( ) ;
0 commit comments