use anyhow::Result;
use chrono::{DateTime, Utc};
use std::sync::Arc;
use uuid::Uuid;
pub trait Entity: Clone + Send + Sync + 'static {
type Service: Send + Sync;
fn resource_name() -> &'static str;
fn resource_name_singular() -> &'static str;
fn service_from_host(host: &Arc<dyn std::any::Any + Send + Sync>)
-> Result<Arc<Self::Service>>;
fn id(&self) -> Uuid;
fn entity_type(&self) -> &str;
fn created_at(&self) -> DateTime<Utc>;
fn updated_at(&self) -> DateTime<Utc>;
fn deleted_at(&self) -> Option<DateTime<Utc>>;
fn status(&self) -> &str;
fn tenant_id(&self) -> Option<Uuid> {
None
}
fn is_deleted(&self) -> bool {
self.deleted_at().is_some()
}
fn is_active(&self) -> bool {
self.status() == "active" && !self.is_deleted()
}
}
pub trait Data: Entity {
fn name(&self) -> &str;
fn indexed_fields() -> &'static [&'static str];
fn field_value(&self, field: &str) -> Option<crate::core::field::FieldValue>;
fn display(&self) {
println!(
"[{}] {} - {} ({})",
self.id(),
self.entity_type(),
self.name(),
self.status()
);
}
}
pub trait Link: Entity {
fn source_id(&self) -> Uuid;
fn target_id(&self) -> Uuid;
fn link_type(&self) -> &str;
fn display(&self) {
println!(
"[{}] {} → {} (type: {}, status: {})",
self.id(),
self.source_id(),
self.target_id(),
self.link_type(),
self.status()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
struct TestEntity {
id: Uuid,
entity_type: String,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
deleted_at: Option<DateTime<Utc>>,
status: String,
}
impl Entity for TestEntity {
type Service = ();
fn resource_name() -> &'static str {
"test_entities"
}
fn resource_name_singular() -> &'static str {
"test_entity"
}
fn service_from_host(
_host: &Arc<dyn std::any::Any + Send + Sync>,
) -> Result<Arc<Self::Service>> {
Ok(Arc::new(()))
}
fn id(&self) -> Uuid {
self.id
}
fn entity_type(&self) -> &str {
&self.entity_type
}
fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
fn updated_at(&self) -> DateTime<Utc> {
self.updated_at
}
fn deleted_at(&self) -> Option<DateTime<Utc>> {
self.deleted_at
}
fn status(&self) -> &str {
&self.status
}
}
#[test]
fn test_entity_is_deleted() {
let now = Utc::now();
let mut entity = TestEntity {
id: Uuid::new_v4(),
entity_type: "test".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "active".to_string(),
};
assert!(!entity.is_deleted());
assert!(entity.is_active());
entity.deleted_at = Some(now);
assert!(entity.is_deleted());
assert!(!entity.is_active());
}
#[test]
fn test_entity_metadata() {
assert_eq!(TestEntity::resource_name(), "test_entities");
assert_eq!(TestEntity::resource_name_singular(), "test_entity");
}
#[test]
fn test_entity_default_tenant_id_is_none() {
let now = Utc::now();
let entity = TestEntity {
id: Uuid::new_v4(),
entity_type: "test".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "active".to_string(),
};
assert_eq!(entity.tenant_id(), None);
}
#[test]
fn test_entity_is_active_with_inactive_status() {
let now = Utc::now();
let entity = TestEntity {
id: Uuid::new_v4(),
entity_type: "test".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "inactive".to_string(),
};
assert!(!entity.is_active());
assert!(!entity.is_deleted());
}
#[test]
fn test_entity_service_from_host() {
let host: Arc<dyn std::any::Any + Send + Sync> = Arc::new(());
let svc = TestEntity::service_from_host(&host).expect("service_from_host should succeed");
assert_eq!(*svc, ());
}
#[derive(Clone, Debug)]
struct TestLink {
id: Uuid,
source_id: Uuid,
target_id: Uuid,
link_type: String,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
deleted_at: Option<DateTime<Utc>>,
status: String,
}
impl Entity for TestLink {
type Service = ();
fn resource_name() -> &'static str {
"test_links"
}
fn resource_name_singular() -> &'static str {
"test_link"
}
fn service_from_host(
_host: &Arc<dyn std::any::Any + Send + Sync>,
) -> Result<Arc<Self::Service>> {
Ok(Arc::new(()))
}
fn id(&self) -> Uuid {
self.id
}
fn entity_type(&self) -> &str {
"test_link"
}
fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
fn updated_at(&self) -> DateTime<Utc> {
self.updated_at
}
fn deleted_at(&self) -> Option<DateTime<Utc>> {
self.deleted_at
}
fn status(&self) -> &str {
&self.status
}
}
impl Link for TestLink {
fn source_id(&self) -> Uuid {
self.source_id
}
fn target_id(&self) -> Uuid {
self.target_id
}
fn link_type(&self) -> &str {
&self.link_type
}
}
#[test]
fn test_link_accessors() {
let now = Utc::now();
let src = Uuid::new_v4();
let tgt = Uuid::new_v4();
let link = TestLink {
id: Uuid::new_v4(),
source_id: src,
target_id: tgt,
link_type: "ownership".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "active".to_string(),
};
assert_eq!(link.source_id(), src);
assert_eq!(link.target_id(), tgt);
assert_eq!(link.link_type(), "ownership");
}
#[test]
fn test_link_is_deleted_and_is_active() {
let now = Utc::now();
let mut link = TestLink {
id: Uuid::new_v4(),
source_id: Uuid::new_v4(),
target_id: Uuid::new_v4(),
link_type: "ref".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "active".to_string(),
};
assert!(!link.is_deleted());
assert!(link.is_active());
link.deleted_at = Some(now);
assert!(link.is_deleted());
assert!(!link.is_active());
}
#[test]
fn test_link_display_does_not_panic() {
let now = Utc::now();
let link = TestLink {
id: Uuid::new_v4(),
source_id: Uuid::new_v4(),
target_id: Uuid::new_v4(),
link_type: "ref".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "active".to_string(),
};
link.display();
}
#[test]
fn test_link_inactive_status() {
let now = Utc::now();
let link = TestLink {
id: Uuid::new_v4(),
source_id: Uuid::new_v4(),
target_id: Uuid::new_v4(),
link_type: "ref".to_string(),
created_at: now,
updated_at: now,
deleted_at: None,
status: "suspended".to_string(),
};
assert!(!link.is_active());
assert!(!link.is_deleted());
}
}