1#[macro_export]
22macro_rules! impl_entity_multi_tenant {
23 ($type:ident) => {
24 impl $type {
28 #[allow(dead_code)]
30 pub fn get_tenant_id(&self) -> ::uuid::Uuid {
31 self.tenant_id
32 }
33 }
34 };
35}
36
37#[macro_export]
41macro_rules! entity_fields {
42 () => {
43 pub id: ::uuid::Uuid,
45
46 #[serde(rename = "type")]
48 pub entity_type: String,
49
50 pub created_at: ::chrono::DateTime<::chrono::Utc>,
52
53 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
55
56 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
58
59 pub status: String,
61 };
62}
63
64#[macro_export]
66macro_rules! data_fields {
67 () => {
68 pub id: ::uuid::Uuid,
70
71 #[serde(rename = "type")]
73 pub entity_type: String,
74
75 pub created_at: ::chrono::DateTime<::chrono::Utc>,
77
78 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
80
81 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
83
84 pub status: String,
86
87 pub name: String,
89 };
90}
91
92#[macro_export]
94macro_rules! link_fields {
95 () => {
96 pub id: ::uuid::Uuid,
98
99 #[serde(rename = "type")]
101 pub entity_type: String,
102
103 pub created_at: ::chrono::DateTime<::chrono::Utc>,
105
106 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
108
109 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
111
112 pub status: String,
114
115 pub link_type: String,
117
118 pub source_id: ::uuid::Uuid,
120
121 pub target_id: ::uuid::Uuid,
123 };
124}
125
126#[macro_export]
154macro_rules! impl_data_entity {
155 (
156 $type:ident,
157 $type_name:expr,
158 [ $( $indexed_field:expr ),* $(,)? ],
159 {
160 $( $specific_field:ident : $specific_type:ty ),* $(,)?
161 }
162 ) => {
163 #[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
164 pub struct $type {
165 pub id: ::uuid::Uuid,
167
168 #[serde(rename = "type")]
170 pub entity_type: String,
171
172 pub created_at: ::chrono::DateTime<::chrono::Utc>,
174
175 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
177
178 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
180
181 pub status: String,
183
184 pub name: String,
186 $( pub $specific_field : $specific_type ),*
187 }
188
189 impl $crate::core::entity::Entity for $type {
191 type Service = ();
192
193 fn resource_name() -> &'static str {
194 use std::sync::OnceLock;
195 static PLURAL: OnceLock<&'static str> = OnceLock::new();
196 PLURAL.get_or_init(|| {
197 Box::leak(
198 $crate::core::pluralize::Pluralizer::pluralize($type_name)
199 .into_boxed_str()
200 )
201 })
202 }
203
204 fn resource_name_singular() -> &'static str {
205 $type_name
206 }
207
208 fn service_from_host(
209 _host: &::std::sync::Arc<dyn ::std::any::Any + Send + Sync>
210 ) -> ::anyhow::Result<::std::sync::Arc<Self::Service>> {
211 unimplemented!("service_from_host must be implemented by user")
212 }
213
214 fn id(&self) -> ::uuid::Uuid {
215 self.id
216 }
217
218 fn entity_type(&self) -> &str {
219 &self.entity_type
220 }
221
222 fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
223 self.created_at
224 }
225
226 fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
227 self.updated_at
228 }
229
230 fn deleted_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> {
231 self.deleted_at
232 }
233
234 fn status(&self) -> &str {
235 &self.status
236 }
237 }
238
239 impl $crate::core::entity::Data for $type {
241 fn name(&self) -> &str {
242 &self.name
243 }
244
245 fn indexed_fields() -> &'static [&'static str] {
246 &[ $( $indexed_field ),* ]
247 }
248
249 fn field_value(&self, field: &str) -> Option<$crate::core::field::FieldValue> {
250 match field {
251 "name" => Some($crate::core::field::FieldValue::String(self.name.clone())),
252 "status" => Some($crate::core::field::FieldValue::String(self.status.clone())),
253 _ => None,
254 }
255 }
256 }
257
258 impl $type {
260 pub fn new(
262 name: String,
263 status: String,
264 $( $specific_field: $specific_type ),*
265 ) -> Self {
266 Self {
267 id: ::uuid::Uuid::new_v4(),
268 entity_type: $type_name.to_string(),
269 created_at: ::chrono::Utc::now(),
270 updated_at: ::chrono::Utc::now(),
271 deleted_at: None,
272 status,
273 name,
274 $( $specific_field ),*
275 }
276 }
277
278 pub fn soft_delete(&mut self) {
280 self.deleted_at = Some(::chrono::Utc::now());
281 self.updated_at = ::chrono::Utc::now();
282 }
283
284 pub fn restore(&mut self) {
286 self.deleted_at = None;
287 self.updated_at = ::chrono::Utc::now();
288 }
289
290 pub fn touch(&mut self) {
292 self.updated_at = ::chrono::Utc::now();
293 }
294
295 pub fn set_status(&mut self, status: String) {
297 self.status = status;
298 self.touch();
299 }
300 }
301 };
302}
303
304#[macro_export]
331macro_rules! impl_link_entity {
332 (
333 $type:ident,
334 $type_name:expr,
335 {
336 $( $specific_field:ident : $specific_type:ty ),* $(,)?
337 }
338 ) => {
339 #[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
340 pub struct $type {
341 pub id: ::uuid::Uuid,
343
344 #[serde(rename = "type")]
346 pub entity_type: String,
347
348 pub created_at: ::chrono::DateTime<::chrono::Utc>,
350
351 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
353
354 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
356
357 pub status: String,
359
360 pub link_type: String,
362
363 pub source_id: ::uuid::Uuid,
365
366 pub target_id: ::uuid::Uuid,
368 $( pub $specific_field : $specific_type ),*
369 }
370
371 impl $crate::core::entity::Entity for $type {
373 type Service = ();
374
375 fn resource_name() -> &'static str {
376 use std::sync::OnceLock;
377 static PLURAL: OnceLock<&'static str> = OnceLock::new();
378 PLURAL.get_or_init(|| {
379 Box::leak(
380 $crate::core::pluralize::Pluralizer::pluralize($type_name)
381 .into_boxed_str()
382 )
383 })
384 }
385
386 fn resource_name_singular() -> &'static str {
387 $type_name
388 }
389
390 fn service_from_host(
391 _host: &::std::sync::Arc<dyn ::std::any::Any + Send + Sync>
392 ) -> ::anyhow::Result<::std::sync::Arc<Self::Service>> {
393 unimplemented!("service_from_host must be implemented by user")
394 }
395
396 fn id(&self) -> ::uuid::Uuid {
397 self.id
398 }
399
400 fn entity_type(&self) -> &str {
401 &self.entity_type
402 }
403
404 fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
405 self.created_at
406 }
407
408 fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
409 self.updated_at
410 }
411
412 fn deleted_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> {
413 self.deleted_at
414 }
415
416 fn status(&self) -> &str {
417 &self.status
418 }
419 }
420
421 impl $crate::core::entity::Link for $type {
423 fn source_id(&self) -> ::uuid::Uuid {
424 self.source_id
425 }
426
427 fn target_id(&self) -> ::uuid::Uuid {
428 self.target_id
429 }
430
431 fn link_type(&self) -> &str {
432 &self.link_type
433 }
434 }
435
436 impl $type {
438 pub fn new(
440 link_type: String,
441 source_id: ::uuid::Uuid,
442 target_id: ::uuid::Uuid,
443 status: String,
444 $( $specific_field: $specific_type ),*
445 ) -> Self {
446 Self {
447 id: ::uuid::Uuid::new_v4(),
448 entity_type: $type_name.to_string(),
449 created_at: ::chrono::Utc::now(),
450 updated_at: ::chrono::Utc::now(),
451 deleted_at: None,
452 status,
453 link_type,
454 source_id,
455 target_id,
456 $( $specific_field ),*
457 }
458 }
459
460 pub fn soft_delete(&mut self) {
462 self.deleted_at = Some(::chrono::Utc::now());
463 self.updated_at = ::chrono::Utc::now();
464 }
465
466 #[allow(dead_code)]
468 pub fn restore(&mut self) {
469 self.deleted_at = None;
470 self.updated_at = ::chrono::Utc::now();
471 }
472
473 #[allow(dead_code)]
475 pub fn touch(&mut self) {
476 self.updated_at = ::chrono::Utc::now();
477 }
478
479 #[allow(dead_code)]
481 pub fn set_status(&mut self, status: String) {
482 self.status = status;
483 self.touch();
484 }
485 }
486 };
487}
488
489#[cfg(test)]
490mod tests {
491 use crate::prelude::*;
492
493 impl_data_entity!(
495 TestUser,
496 "test_user",
497 ["name", "email"],
498 {
499 email: String,
500 }
501 );
502
503 impl_link_entity!(
505 TestOwnerLink,
506 "test_owner_link",
507 {
508 since: DateTime<Utc>,
509 }
510 );
511
512 #[test]
513 fn test_data_entity_creation() {
514 let user = TestUser::new(
515 "John Doe".to_string(),
516 "active".to_string(),
517 "john@example.com".to_string(),
518 );
519
520 assert_eq!(user.name(), "John Doe");
521 assert_eq!(user.status(), "active");
522 assert_eq!(user.email, "john@example.com");
523 assert!(!user.is_deleted());
524 assert!(user.is_active());
525 }
526
527 #[test]
528 fn test_data_entity_soft_delete() {
529 let mut user = TestUser::new(
530 "John Doe".to_string(),
531 "active".to_string(),
532 "john@example.com".to_string(),
533 );
534
535 assert!(!user.is_deleted());
536 user.soft_delete();
537 assert!(user.is_deleted());
538 assert!(!user.is_active());
539 }
540
541 #[test]
542 fn test_data_entity_restore() {
543 let mut user = TestUser::new(
544 "John Doe".to_string(),
545 "active".to_string(),
546 "john@example.com".to_string(),
547 );
548
549 user.soft_delete();
550 assert!(user.is_deleted());
551
552 user.restore();
553 assert!(!user.is_deleted());
554 assert!(user.is_active());
555 }
556
557 #[test]
558 fn test_link_entity_creation() {
559 let user_id = Uuid::new_v4();
560 let car_id = Uuid::new_v4();
561
562 let link = TestOwnerLink::new(
563 "owner".to_string(),
564 user_id,
565 car_id,
566 "active".to_string(),
567 Utc::now(),
568 );
569
570 assert_eq!(link.source_id(), user_id);
571 assert_eq!(link.target_id(), car_id);
572 assert_eq!(link.link_type(), "owner");
573 assert_eq!(link.status(), "active");
574 assert!(!link.is_deleted());
575 }
576
577 #[test]
578 fn test_link_entity_soft_delete() {
579 let link = TestOwnerLink::new(
580 "owner".to_string(),
581 Uuid::new_v4(),
582 Uuid::new_v4(),
583 "active".to_string(),
584 Utc::now(),
585 );
586
587 let mut link = link;
588 assert!(!link.is_deleted());
589
590 link.soft_delete();
591 assert!(link.is_deleted());
592 }
593
594 #[test]
595 fn test_entity_set_status() {
596 let mut user = TestUser::new(
597 "John Doe".to_string(),
598 "active".to_string(),
599 "john@example.com".to_string(),
600 );
601
602 assert_eq!(user.status(), "active");
603
604 user.set_status("inactive".to_string());
605 assert_eq!(user.status(), "inactive");
606 }
607}