1use anyhow::Result;
4use chrono::{DateTime, Utc};
5use std::sync::Arc;
6use uuid::Uuid;
7
8pub trait Entity: Clone + Send + Sync + 'static {
19 type Service: Send + Sync;
21
22 fn resource_name() -> &'static str;
24
25 fn resource_name_singular() -> &'static str;
27
28 fn service_from_host(host: &Arc<dyn std::any::Any + Send + Sync>)
30 -> Result<Arc<Self::Service>>;
31
32 fn id(&self) -> Uuid;
36
37 fn entity_type(&self) -> &str;
39
40 fn created_at(&self) -> DateTime<Utc>;
42
43 fn updated_at(&self) -> DateTime<Utc>;
45
46 fn deleted_at(&self) -> Option<DateTime<Utc>>;
48
49 fn status(&self) -> &str;
51
52 fn tenant_id(&self) -> Option<Uuid> {
69 None
70 }
71
72 fn is_deleted(&self) -> bool {
74 self.deleted_at().is_some()
75 }
76
77 fn is_active(&self) -> bool {
79 self.status() == "active" && !self.is_deleted()
80 }
81}
82
83pub trait Data: Entity {
90 fn name(&self) -> &str;
92
93 fn indexed_fields() -> &'static [&'static str];
95
96 fn field_value(&self, field: &str) -> Option<crate::core::field::FieldValue>;
98
99 fn display(&self) {
101 println!(
102 "[{}] {} - {} ({})",
103 self.id(),
104 self.entity_type(),
105 self.name(),
106 self.status()
107 );
108 }
109}
110
111pub trait Link: Entity {
118 fn source_id(&self) -> Uuid;
120
121 fn target_id(&self) -> Uuid;
123
124 fn link_type(&self) -> &str;
126
127 fn display(&self) {
129 println!(
130 "[{}] {} → {} (type: {}, status: {})",
131 self.id(),
132 self.source_id(),
133 self.target_id(),
134 self.link_type(),
135 self.status()
136 );
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use serde::{Deserialize, Serialize};
144
145 #[derive(Clone, Debug, Serialize, Deserialize)]
147 struct TestEntity {
148 id: Uuid,
149 entity_type: String,
150 created_at: DateTime<Utc>,
151 updated_at: DateTime<Utc>,
152 deleted_at: Option<DateTime<Utc>>,
153 status: String,
154 }
155
156 impl Entity for TestEntity {
157 type Service = ();
158
159 fn resource_name() -> &'static str {
160 "test_entities"
161 }
162
163 fn resource_name_singular() -> &'static str {
164 "test_entity"
165 }
166
167 fn service_from_host(
168 _host: &Arc<dyn std::any::Any + Send + Sync>,
169 ) -> Result<Arc<Self::Service>> {
170 Ok(Arc::new(()))
171 }
172
173 fn id(&self) -> Uuid {
174 self.id
175 }
176
177 fn entity_type(&self) -> &str {
178 &self.entity_type
179 }
180
181 fn created_at(&self) -> DateTime<Utc> {
182 self.created_at
183 }
184
185 fn updated_at(&self) -> DateTime<Utc> {
186 self.updated_at
187 }
188
189 fn deleted_at(&self) -> Option<DateTime<Utc>> {
190 self.deleted_at
191 }
192
193 fn status(&self) -> &str {
194 &self.status
195 }
196 }
197
198 #[test]
199 fn test_entity_is_deleted() {
200 let now = Utc::now();
201 let mut entity = TestEntity {
202 id: Uuid::new_v4(),
203 entity_type: "test".to_string(),
204 created_at: now,
205 updated_at: now,
206 deleted_at: None,
207 status: "active".to_string(),
208 };
209
210 assert!(!entity.is_deleted());
211 assert!(entity.is_active());
212
213 entity.deleted_at = Some(now);
214 assert!(entity.is_deleted());
215 assert!(!entity.is_active());
216 }
217
218 #[test]
219 fn test_entity_metadata() {
220 assert_eq!(TestEntity::resource_name(), "test_entities");
221 assert_eq!(TestEntity::resource_name_singular(), "test_entity");
222 }
223
224 #[test]
225 fn test_entity_default_tenant_id_is_none() {
226 let now = Utc::now();
227 let entity = TestEntity {
228 id: Uuid::new_v4(),
229 entity_type: "test".to_string(),
230 created_at: now,
231 updated_at: now,
232 deleted_at: None,
233 status: "active".to_string(),
234 };
235 assert_eq!(entity.tenant_id(), None);
236 }
237
238 #[test]
239 fn test_entity_is_active_with_inactive_status() {
240 let now = Utc::now();
241 let entity = TestEntity {
242 id: Uuid::new_v4(),
243 entity_type: "test".to_string(),
244 created_at: now,
245 updated_at: now,
246 deleted_at: None,
247 status: "inactive".to_string(),
248 };
249 assert!(!entity.is_active());
250 assert!(!entity.is_deleted());
251 }
252
253 #[test]
254 fn test_entity_service_from_host() {
255 let host: Arc<dyn std::any::Any + Send + Sync> = Arc::new(());
256 let svc = TestEntity::service_from_host(&host).expect("service_from_host should succeed");
257 assert_eq!(*svc, ());
259 }
260
261 #[derive(Clone, Debug)]
264 struct TestLink {
265 id: Uuid,
266 source_id: Uuid,
267 target_id: Uuid,
268 link_type: String,
269 created_at: DateTime<Utc>,
270 updated_at: DateTime<Utc>,
271 deleted_at: Option<DateTime<Utc>>,
272 status: String,
273 }
274
275 impl Entity for TestLink {
276 type Service = ();
277
278 fn resource_name() -> &'static str {
279 "test_links"
280 }
281
282 fn resource_name_singular() -> &'static str {
283 "test_link"
284 }
285
286 fn service_from_host(
287 _host: &Arc<dyn std::any::Any + Send + Sync>,
288 ) -> Result<Arc<Self::Service>> {
289 Ok(Arc::new(()))
290 }
291
292 fn id(&self) -> Uuid {
293 self.id
294 }
295
296 fn entity_type(&self) -> &str {
297 "test_link"
298 }
299
300 fn created_at(&self) -> DateTime<Utc> {
301 self.created_at
302 }
303
304 fn updated_at(&self) -> DateTime<Utc> {
305 self.updated_at
306 }
307
308 fn deleted_at(&self) -> Option<DateTime<Utc>> {
309 self.deleted_at
310 }
311
312 fn status(&self) -> &str {
313 &self.status
314 }
315 }
316
317 impl Link for TestLink {
318 fn source_id(&self) -> Uuid {
319 self.source_id
320 }
321
322 fn target_id(&self) -> Uuid {
323 self.target_id
324 }
325
326 fn link_type(&self) -> &str {
327 &self.link_type
328 }
329 }
330
331 #[test]
332 fn test_link_accessors() {
333 let now = Utc::now();
334 let src = Uuid::new_v4();
335 let tgt = Uuid::new_v4();
336 let link = TestLink {
337 id: Uuid::new_v4(),
338 source_id: src,
339 target_id: tgt,
340 link_type: "ownership".to_string(),
341 created_at: now,
342 updated_at: now,
343 deleted_at: None,
344 status: "active".to_string(),
345 };
346 assert_eq!(link.source_id(), src);
347 assert_eq!(link.target_id(), tgt);
348 assert_eq!(link.link_type(), "ownership");
349 }
350
351 #[test]
352 fn test_link_is_deleted_and_is_active() {
353 let now = Utc::now();
354 let mut link = TestLink {
355 id: Uuid::new_v4(),
356 source_id: Uuid::new_v4(),
357 target_id: Uuid::new_v4(),
358 link_type: "ref".to_string(),
359 created_at: now,
360 updated_at: now,
361 deleted_at: None,
362 status: "active".to_string(),
363 };
364 assert!(!link.is_deleted());
365 assert!(link.is_active());
366
367 link.deleted_at = Some(now);
368 assert!(link.is_deleted());
369 assert!(!link.is_active());
370 }
371
372 #[test]
373 fn test_link_display_does_not_panic() {
374 let now = Utc::now();
375 let link = TestLink {
376 id: Uuid::new_v4(),
377 source_id: Uuid::new_v4(),
378 target_id: Uuid::new_v4(),
379 link_type: "ref".to_string(),
380 created_at: now,
381 updated_at: now,
382 deleted_at: None,
383 status: "active".to_string(),
384 };
385 link.display();
387 }
388
389 #[test]
390 fn test_link_inactive_status() {
391 let now = Utc::now();
392 let link = TestLink {
393 id: Uuid::new_v4(),
394 source_id: Uuid::new_v4(),
395 target_id: Uuid::new_v4(),
396 link_type: "ref".to_string(),
397 created_at: now,
398 updated_at: now,
399 deleted_at: None,
400 status: "suspended".to_string(),
401 };
402 assert!(!link.is_active());
403 assert!(!link.is_deleted());
404 }
405}