Skip to main content

rustbasic_core/
macros.rs

1#[macro_export]
2#[doc(hidden)]
3macro_rules! model_timestamps_fields {
4    (true) => {
5        pub created_at: Option<DateTime>,
6        pub updated_at: Option<DateTime>,
7    };
8    (false) => {};
9}
10
11#[macro_export]
12#[doc(hidden)]
13macro_rules! model_behavior_impl {
14    (true) => {
15        #[$crate::async_trait]
16        impl $crate::sea_orm::entity::prelude::ActiveModelBehavior for ActiveModel {
17            async fn before_save<C>(mut self, _db: &C, insert: bool) -> Result<Self, $crate::sea_orm::DbErr>
18            where
19                C: $crate::sea_orm::ConnectionTrait,
20            {
21                let now = $crate::chrono::Local::now().naive_local();
22                if insert {
23                    self.created_at = $crate::sea_orm::ActiveValue::Set(Some(now));
24                }
25                self.updated_at = $crate::sea_orm::ActiveValue::Set(Some(now));
26                Ok(self)
27            }
28        }
29    };
30    (false) => {
31        impl $crate::sea_orm::entity::prelude::ActiveModelBehavior for ActiveModel {}
32    };
33}
34
35#[macro_export]
36#[doc(hidden)]
37macro_rules! model_impl {
38    // Branch A: timestamps: true
39    (
40        table: $table_name:expr,
41        timestamps: true,
42        fillable: [ $($fill:ident),* ],
43        guarded: [ $($guard:ident),* ],
44        Model {
45            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
46        }
47    ) => {
48        #[derive(Clone, Debug, PartialEq, $crate::sea_orm::entity::prelude::DeriveEntityModel, $crate::serde::Serialize, $crate::serde::Deserialize)]
49        #[sea_orm(table_name = $table_name)]
50        pub struct Model {
51            $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
52            pub created_at: Option<DateTime>,
53            pub updated_at: Option<DateTime>,
54        }
55
56        #[derive(Copy, Clone, Debug, $crate::sea_orm::entity::prelude::EnumIter, $crate::sea_orm::entity::prelude::DeriveRelation)]
57        pub enum Relation {}
58
59        $crate::model_behavior_impl!(true);
60
61        impl Entity {
62            pub async fn create<C>(db: &C, data: $crate::serde_json::Value) -> Result<Model, $crate::sea_orm::DbErr>
63            where
64                C: $crate::sea_orm::ConnectionTrait,
65            {
66                let active = ActiveModel::fill(&data)?;
67                <ActiveModel as $crate::sea_orm::ActiveModelTrait>::insert(active, db).await
68            }
69        }
70
71        impl Model {
72            pub async fn create<C>(db: &C, data: $crate::serde_json::Value) -> Result<Self, $crate::sea_orm::DbErr>
73            where
74                C: $crate::sea_orm::ConnectionTrait,
75            {
76                let active = ActiveModel::fill(&data)?;
77                <ActiveModel as $crate::sea_orm::ActiveModelTrait>::insert(active, db).await
78            }
79        }
80
81        impl ActiveModel {
82            pub fn fill(json: &$crate::serde_json::Value) -> Result<Self, $crate::sea_orm::DbErr> {
83                let mut sanitized = $crate::serde_json::Map::new();
84                if let Some(obj) = json.as_object() {
85                    let fillable: Vec<&str> = vec![ $( stringify!($fill) ),* ];
86                    let guarded: Vec<&str> = vec![ $( stringify!($guard) ),* ];
87                    
88                    if !fillable.is_empty() {
89                        for key in fillable {
90                            if let Some(val) = obj.get(key) {
91                                sanitized.insert(key.to_string(), val.clone());
92                            }
93                        }
94                    } else if !guarded.is_empty() {
95                        for (key, val) in obj {
96                            if !guarded.contains(&key.as_str()) {
97                                sanitized.insert(key.clone(), val.clone());
98                            }
99                        }
100                    } else {
101                        for (key, val) in obj {
102                            sanitized.insert(key.clone(), val.clone());
103                        }
104                    }
105                }
106                Self::from_json($crate::serde_json::Value::Object(sanitized))
107            }
108        }
109    };
110
111    // Branch B: timestamps: false
112    (
113        table: $table_name:expr,
114        timestamps: false,
115        fillable: [ $($fill:ident),* ],
116        guarded: [ $($guard:ident),* ],
117        Model {
118            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
119        }
120    ) => {
121        #[derive(Clone, Debug, PartialEq, $crate::sea_orm::entity::prelude::DeriveEntityModel, $crate::serde::Serialize, $crate::serde::Deserialize)]
122        #[sea_orm(table_name = $table_name)]
123        pub struct Model {
124            $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
125        }
126
127        #[derive(Copy, Clone, Debug, $crate::sea_orm::entity::prelude::EnumIter, $crate::sea_orm::entity::prelude::DeriveRelation)]
128        pub enum Relation {}
129
130        $crate::model_behavior_impl!(false);
131
132        impl Entity {
133            pub async fn create<C>(db: &C, data: $crate::serde_json::Value) -> Result<Model, $crate::sea_orm::DbErr>
134            where
135                C: $crate::sea_orm::ConnectionTrait,
136            {
137                let active = ActiveModel::fill(&data)?;
138                <ActiveModel as $crate::sea_orm::ActiveModelTrait>::insert(active, db).await
139            }
140        }
141
142        impl Model {
143            pub async fn create<C>(db: &C, data: $crate::serde_json::Value) -> Result<Self, $crate::sea_orm::DbErr>
144            where
145                C: $crate::sea_orm::ConnectionTrait,
146            {
147                let active = ActiveModel::fill(&data)?;
148                <ActiveModel as $crate::sea_orm::ActiveModelTrait>::insert(active, db).await
149            }
150        }
151
152        impl ActiveModel {
153            pub fn fill(json: &$crate::serde_json::Value) -> Result<Self, $crate::sea_orm::DbErr> {
154                let mut sanitized = $crate::serde_json::Map::new();
155                if let Some(obj) = json.as_object() {
156                    let fillable: Vec<&str> = vec![ $( stringify!($fill) ),* ];
157                    let guarded: Vec<&str> = vec![ $( stringify!($guard) ),* ];
158                    
159                    if !fillable.is_empty() {
160                        for key in fillable {
161                            if let Some(val) = obj.get(key) {
162                                sanitized.insert(key.to_string(), val.clone());
163                            }
164                        }
165                    } else if !guarded.is_empty() {
166                        for (key, val) in obj {
167                            if !guarded.contains(&key.as_str()) {
168                                sanitized.insert(key.clone(), val.clone());
169                            }
170                        }
171                    } else {
172                        for (key, val) in obj {
173                            sanitized.insert(key.clone(), val.clone());
174                        }
175                    }
176                }
177                Self::from_json($crate::serde_json::Value::Object(sanitized))
178            }
179        }
180    };
181}
182
183#[macro_export]
184macro_rules! model {
185    // 1. All options provided with true
186    (
187        table: $table_name:expr,
188        timestamps: true,
189        fillable: [ $($fill:ident),* ],
190        guarded: [ $($guard:ident),* ],
191        Model {
192            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
193        }
194    ) => {
195        $crate::model_impl! {
196            table: $table_name,
197            timestamps: true,
198            fillable: [ $($fill),* ],
199            guarded: [ $($guard),* ],
200            Model {
201                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
202            }
203        }
204    };
205
206    // 2. All options provided with false
207    (
208        table: $table_name:expr,
209        timestamps: false,
210        fillable: [ $($fill:ident),* ],
211        guarded: [ $($guard:ident),* ],
212        Model {
213            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
214        }
215    ) => {
216        $crate::model_impl! {
217            table: $table_name,
218            timestamps: false,
219            fillable: [ $($fill),* ],
220            guarded: [ $($guard),* ],
221            Model {
222                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
223            }
224        }
225    };
226
227    // 3. Defaulting guarded (empty)
228    (
229        table: $table_name:expr,
230        timestamps: $ts:ident,
231        fillable: [ $($fill:ident),* ],
232        Model {
233            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
234        }
235    ) => {
236        $crate::model! {
237            table: $table_name,
238            timestamps: $ts,
239            fillable: [ $($fill),* ],
240            guarded: [ ],
241            Model {
242                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
243            }
244        }
245    };
246
247    // 4. Defaulting fillable (empty)
248    (
249        table: $table_name:expr,
250        timestamps: $ts:ident,
251        guarded: [ $($guard:ident),* ],
252        Model {
253            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
254        }
255    ) => {
256        $crate::model! {
257            table: $table_name,
258            timestamps: $ts,
259            fillable: [ ],
260            guarded: [ $($guard),* ],
261            Model {
262                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
263            }
264        }
265    };
266
267    // 5. Defaulting both fillable and guarded (empty)
268    (
269        table: $table_name:expr,
270        timestamps: $ts:ident,
271        Model {
272            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
273        }
274    ) => {
275        $crate::model! {
276            table: $table_name,
277            timestamps: $ts,
278            fillable: [ ],
279            guarded: [ ],
280            Model {
281                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
282            }
283        }
284    };
285
286    // 6. Defaulting timestamps: true
287    (
288        table: $table_name:expr,
289        fillable: [ $($fill:ident),* ],
290        guarded: [ $($guard:ident),* ],
291        Model {
292            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
293        }
294    ) => {
295        $crate::model! {
296            table: $table_name,
297            timestamps: true,
298            fillable: [ $($fill),* ],
299            guarded: [ $($guard),* ],
300            Model {
301                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
302            }
303        }
304    };
305
306    // 7. Defaulting timestamps: true, guarded: empty
307    (
308        table: $table_name:expr,
309        fillable: [ $($fill:ident),* ],
310        Model {
311            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
312        }
313    ) => {
314        $crate::model! {
315            table: $table_name,
316            timestamps: true,
317            fillable: [ $($fill),* ],
318            guarded: [ ],
319            Model {
320                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
321            }
322        }
323    };
324
325    // 8. Defaulting timestamps: true, fillable: empty
326    (
327        table: $table_name:expr,
328        guarded: [ $($guard:ident),* ],
329        Model {
330            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
331        }
332    ) => {
333        $crate::model! {
334            table: $table_name,
335            timestamps: true,
336            fillable: [ ],
337            guarded: [ $($guard),* ],
338            Model {
339                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
340            }
341        }
342    };
343
344    // 9. Defaulting all: timestamps: true, fillable: empty, guarded: empty
345    (
346        table: $table_name:expr,
347        Model {
348            $($(#[$field_meta:meta])* $field_vis:vis $field_name:ident : $field_type:ty),* $(,)?
349        }
350    ) => {
351        $crate::model! {
352            table: $table_name,
353            timestamps: true,
354            fillable: [ ],
355            guarded: [ ],
356            Model {
357                $($(#[$field_meta])* $field_vis $field_name : $field_type,)*
358            }
359        }
360    };
361}
362
363#[macro_export]
364macro_rules! seeder {
365    (
366        $name:ident,
367        run($db:ident) $body:block
368    ) => {
369        pub struct $name;
370
371        #[$crate::async_trait]
372        impl $crate::seeder::SeederTrait for $name {
373            async fn run(&self, $db: &$crate::sea_orm::DatabaseConnection) -> Result<(), $crate::sea_orm::DbErr> {
374                $body
375            }
376        }
377    };
378
379    (
380        run($db:ident) $body:block
381    ) => {
382        $crate::seeder! {
383            DatabaseSeeder,
384            run($db) $body
385        }
386    };
387}