1#[macro_export]
10macro_rules! entity_fields {
11 () => {
12 pub id: ::uuid::Uuid,
14
15 #[serde(rename = "type")]
17 pub entity_type: String,
18
19 pub created_at: ::chrono::DateTime<::chrono::Utc>,
21
22 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
24
25 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
27
28 pub status: String,
30 };
31}
32
33#[macro_export]
35macro_rules! data_fields {
36 () => {
37 pub id: ::uuid::Uuid,
39
40 #[serde(rename = "type")]
42 pub entity_type: String,
43
44 pub created_at: ::chrono::DateTime<::chrono::Utc>,
46
47 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
49
50 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
52
53 pub status: String,
55
56 pub name: String,
58 };
59}
60
61#[macro_export]
63macro_rules! link_fields {
64 () => {
65 pub id: ::uuid::Uuid,
67
68 #[serde(rename = "type")]
70 pub entity_type: String,
71
72 pub created_at: ::chrono::DateTime<::chrono::Utc>,
74
75 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
77
78 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
80
81 pub status: String,
83
84 pub link_type: String,
86
87 pub source_id: ::uuid::Uuid,
89
90 pub target_id: ::uuid::Uuid,
92 };
93}
94
95#[macro_export]
123macro_rules! impl_data_entity {
124 (
125 $type:ident,
126 $type_name:expr,
127 [ $( $indexed_field:expr ),* $(,)? ],
128 {
129 $( $specific_field:ident : $specific_type:ty ),* $(,)?
130 }
131 ) => {
132 #[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
133 pub struct $type {
134 pub id: ::uuid::Uuid,
136
137 #[serde(rename = "type")]
139 pub entity_type: String,
140
141 pub created_at: ::chrono::DateTime<::chrono::Utc>,
143
144 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
146
147 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
149
150 pub status: String,
152
153 pub name: String,
155 $( pub $specific_field : $specific_type ),*
156 }
157
158 impl $crate::core::entity::Entity for $type {
160 type Service = ();
161
162 fn resource_name() -> &'static str {
163 use std::sync::OnceLock;
164 static PLURAL: OnceLock<&'static str> = OnceLock::new();
165 PLURAL.get_or_init(|| {
166 Box::leak(
167 $crate::core::pluralize::Pluralizer::pluralize($type_name)
168 .into_boxed_str()
169 )
170 })
171 }
172
173 fn resource_name_singular() -> &'static str {
174 $type_name
175 }
176
177 fn service_from_host(
178 _host: &::std::sync::Arc<dyn ::std::any::Any + Send + Sync>
179 ) -> ::anyhow::Result<::std::sync::Arc<Self::Service>> {
180 unimplemented!("service_from_host must be implemented by user")
181 }
182
183 fn id(&self) -> ::uuid::Uuid {
184 self.id
185 }
186
187 fn entity_type(&self) -> &str {
188 &self.entity_type
189 }
190
191 fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
192 self.created_at
193 }
194
195 fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
196 self.updated_at
197 }
198
199 fn deleted_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> {
200 self.deleted_at
201 }
202
203 fn status(&self) -> &str {
204 &self.status
205 }
206 }
207
208 impl $crate::core::entity::Data for $type {
210 fn name(&self) -> &str {
211 &self.name
212 }
213
214 fn indexed_fields() -> &'static [&'static str] {
215 &[ $( $indexed_field ),* ]
216 }
217
218 fn field_value(&self, field: &str) -> Option<$crate::core::field::FieldValue> {
219 match field {
220 "name" => Some($crate::core::field::FieldValue::String(self.name.clone())),
221 "status" => Some($crate::core::field::FieldValue::String(self.status.clone())),
222 _ => None,
223 }
224 }
225 }
226
227 impl $type {
229 pub fn new(
231 name: String,
232 status: String,
233 $( $specific_field: $specific_type ),*
234 ) -> Self {
235 Self {
236 id: ::uuid::Uuid::new_v4(),
237 entity_type: $type_name.to_string(),
238 created_at: ::chrono::Utc::now(),
239 updated_at: ::chrono::Utc::now(),
240 deleted_at: None,
241 status,
242 name,
243 $( $specific_field ),*
244 }
245 }
246
247 pub fn soft_delete(&mut self) {
249 self.deleted_at = Some(::chrono::Utc::now());
250 self.updated_at = ::chrono::Utc::now();
251 }
252
253 pub fn restore(&mut self) {
255 self.deleted_at = None;
256 self.updated_at = ::chrono::Utc::now();
257 }
258
259 pub fn touch(&mut self) {
261 self.updated_at = ::chrono::Utc::now();
262 }
263
264 pub fn set_status(&mut self, status: String) {
266 self.status = status;
267 self.touch();
268 }
269 }
270 };
271}
272
273#[macro_export]
300macro_rules! impl_link_entity {
301 (
302 $type:ident,
303 $type_name:expr,
304 {
305 $( $specific_field:ident : $specific_type:ty ),* $(,)?
306 }
307 ) => {
308 #[derive(Debug, Clone, ::serde::Serialize, ::serde::Deserialize)]
309 pub struct $type {
310 pub id: ::uuid::Uuid,
312
313 #[serde(rename = "type")]
315 pub entity_type: String,
316
317 pub created_at: ::chrono::DateTime<::chrono::Utc>,
319
320 pub updated_at: ::chrono::DateTime<::chrono::Utc>,
322
323 pub deleted_at: Option<::chrono::DateTime<::chrono::Utc>>,
325
326 pub status: String,
328
329 pub link_type: String,
331
332 pub source_id: ::uuid::Uuid,
334
335 pub target_id: ::uuid::Uuid,
337 $( pub $specific_field : $specific_type ),*
338 }
339
340 impl $crate::core::entity::Entity for $type {
342 type Service = ();
343
344 fn resource_name() -> &'static str {
345 use std::sync::OnceLock;
346 static PLURAL: OnceLock<&'static str> = OnceLock::new();
347 PLURAL.get_or_init(|| {
348 Box::leak(
349 $crate::core::pluralize::Pluralizer::pluralize($type_name)
350 .into_boxed_str()
351 )
352 })
353 }
354
355 fn resource_name_singular() -> &'static str {
356 $type_name
357 }
358
359 fn service_from_host(
360 _host: &::std::sync::Arc<dyn ::std::any::Any + Send + Sync>
361 ) -> ::anyhow::Result<::std::sync::Arc<Self::Service>> {
362 unimplemented!("service_from_host must be implemented by user")
363 }
364
365 fn id(&self) -> ::uuid::Uuid {
366 self.id
367 }
368
369 fn entity_type(&self) -> &str {
370 &self.entity_type
371 }
372
373 fn created_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
374 self.created_at
375 }
376
377 fn updated_at(&self) -> ::chrono::DateTime<::chrono::Utc> {
378 self.updated_at
379 }
380
381 fn deleted_at(&self) -> Option<::chrono::DateTime<::chrono::Utc>> {
382 self.deleted_at
383 }
384
385 fn status(&self) -> &str {
386 &self.status
387 }
388 }
389
390 impl $crate::core::entity::Link for $type {
392 fn source_id(&self) -> ::uuid::Uuid {
393 self.source_id
394 }
395
396 fn target_id(&self) -> ::uuid::Uuid {
397 self.target_id
398 }
399
400 fn link_type(&self) -> &str {
401 &self.link_type
402 }
403 }
404
405 impl $type {
407 pub fn new(
409 link_type: String,
410 source_id: ::uuid::Uuid,
411 target_id: ::uuid::Uuid,
412 status: String,
413 $( $specific_field: $specific_type ),*
414 ) -> Self {
415 Self {
416 id: ::uuid::Uuid::new_v4(),
417 entity_type: $type_name.to_string(),
418 created_at: ::chrono::Utc::now(),
419 updated_at: ::chrono::Utc::now(),
420 deleted_at: None,
421 status,
422 link_type,
423 source_id,
424 target_id,
425 $( $specific_field ),*
426 }
427 }
428
429 pub fn soft_delete(&mut self) {
431 self.deleted_at = Some(::chrono::Utc::now());
432 self.updated_at = ::chrono::Utc::now();
433 }
434
435 #[allow(dead_code)]
437 pub fn restore(&mut self) {
438 self.deleted_at = None;
439 self.updated_at = ::chrono::Utc::now();
440 }
441
442 #[allow(dead_code)]
444 pub fn touch(&mut self) {
445 self.updated_at = ::chrono::Utc::now();
446 }
447
448 #[allow(dead_code)]
450 pub fn set_status(&mut self, status: String) {
451 self.status = status;
452 self.touch();
453 }
454 }
455 };
456}
457
458#[cfg(test)]
459mod tests {
460 use crate::prelude::*;
461
462 impl_data_entity!(
464 TestUser,
465 "test_user",
466 ["name", "email"],
467 {
468 email: String,
469 }
470 );
471
472 impl_link_entity!(
474 TestOwnerLink,
475 "test_owner_link",
476 {
477 since: DateTime<Utc>,
478 }
479 );
480
481 #[test]
482 fn test_data_entity_creation() {
483 let user = TestUser::new(
484 "John Doe".to_string(),
485 "active".to_string(),
486 "john@example.com".to_string(),
487 );
488
489 assert_eq!(user.name(), "John Doe");
490 assert_eq!(user.status(), "active");
491 assert_eq!(user.email, "john@example.com");
492 assert!(!user.is_deleted());
493 assert!(user.is_active());
494 }
495
496 #[test]
497 fn test_data_entity_soft_delete() {
498 let mut user = TestUser::new(
499 "John Doe".to_string(),
500 "active".to_string(),
501 "john@example.com".to_string(),
502 );
503
504 assert!(!user.is_deleted());
505 user.soft_delete();
506 assert!(user.is_deleted());
507 assert!(!user.is_active());
508 }
509
510 #[test]
511 fn test_data_entity_restore() {
512 let mut user = TestUser::new(
513 "John Doe".to_string(),
514 "active".to_string(),
515 "john@example.com".to_string(),
516 );
517
518 user.soft_delete();
519 assert!(user.is_deleted());
520
521 user.restore();
522 assert!(!user.is_deleted());
523 assert!(user.is_active());
524 }
525
526 #[test]
527 fn test_link_entity_creation() {
528 let user_id = Uuid::new_v4();
529 let car_id = Uuid::new_v4();
530
531 let link = TestOwnerLink::new(
532 "owner".to_string(),
533 user_id,
534 car_id,
535 "active".to_string(),
536 Utc::now(),
537 );
538
539 assert_eq!(link.source_id(), user_id);
540 assert_eq!(link.target_id(), car_id);
541 assert_eq!(link.link_type(), "owner");
542 assert_eq!(link.status(), "active");
543 assert!(!link.is_deleted());
544 }
545
546 #[test]
547 fn test_link_entity_soft_delete() {
548 let link = TestOwnerLink::new(
549 "owner".to_string(),
550 Uuid::new_v4(),
551 Uuid::new_v4(),
552 "active".to_string(),
553 Utc::now(),
554 );
555
556 let mut link = link;
557 assert!(!link.is_deleted());
558
559 link.soft_delete();
560 assert!(link.is_deleted());
561 }
562
563 #[test]
564 fn test_entity_set_status() {
565 let mut user = TestUser::new(
566 "John Doe".to_string(),
567 "active".to_string(),
568 "john@example.com".to_string(),
569 );
570
571 assert_eq!(user.status(), "active");
572
573 user.set_status("inactive".to_string());
574 assert_eq!(user.status(), "inactive");
575 }
576}