use heromodels_derive::model; use serde::{Deserialize, Serialize}; // Make the current crate visible as an extern crate named `heromodels_core` extern crate self as heromodels_core; extern crate serde_json; // ensure ::serde_json path resolves // Mock the heromodels_core API at crate root (visible via the alias above) #[derive(Debug, Clone, PartialEq, Eq)] pub struct IndexKey { pub name: &'static str, pub value: String, } pub trait Model: std::fmt::Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static { fn db_prefix() -> &'static str where Self: Sized; fn get_id(&self) -> u32; fn base_data_mut(&mut self) -> &mut BaseModelData; fn db_keys(&self) -> Vec { Vec::new() } fn indexed_fields() -> Vec<&'static str> { Vec::new() } } pub trait Index { type Model: Model; type Key: ToString + ?Sized; fn key() -> &'static str; fn field_name() -> &'static str; } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BaseModelData { pub id: u32, pub created_at: i64, pub modified_at: i64, pub comments: Vec, } impl BaseModelData { pub fn new() -> Self { let now = 1000; Self { id: 0, created_at: now, modified_at: now, comments: Vec::new() } } pub fn update_modified(&mut self) { self.modified_at += 1; } } // Top-level field index tests #[derive(Debug, Clone, Serialize, Deserialize)] #[model] pub struct TestUser { base_data: heromodels_core::BaseModelData, #[index] username: String, #[index] is_active: bool, } #[test] fn test_basic_model() { assert_eq!(TestUser::db_prefix(), "test_user"); let user = TestUser { base_data: heromodels_core::BaseModelData::new(), username: "test".to_string(), is_active: true, }; let keys = user.db_keys(); assert_eq!(keys.len(), 2); assert_eq!(keys[0].name, "username"); assert_eq!(keys[0].value, "test"); assert_eq!(keys[1].name, "is_active"); assert_eq!(keys[1].value, "true"); } // Nested path index tests (including vector traversal) #[derive(Debug, Clone, Serialize, Deserialize, Default)] struct GPU { gpu_brand: String } #[derive(Debug, Clone, Serialize, Deserialize, Default)] struct CPU { cpu_brand: String } #[derive(Debug, Clone, Serialize, Deserialize, Default)] struct DeviceInfo { vendor: String, cpu: Vec, gpu: Vec } #[derive(Debug, Clone, Serialize, Deserialize)] #[model] pub struct NodeLike { base_data: heromodels_core::BaseModelData, #[index(path = "vendor")] #[index(path = "cpu.cpu_brand")] #[index(path = "gpu.gpu_brand")] devices: DeviceInfo, } #[test] fn test_nested_indexes() { let n = NodeLike { base_data: heromodels_core::BaseModelData::new(), devices: DeviceInfo { vendor: "SuperVendor".to_string(), cpu: vec![CPU { cpu_brand: "Intel".into() }, CPU { cpu_brand: "AMD".into() }], gpu: vec![GPU { gpu_brand: "NVIDIA".into() }, GPU { gpu_brand: "AMD".into() }], }, }; let mut keys = n.db_keys(); // Sort for deterministic assertions keys.sort_by(|a,b| a.name.cmp(b.name).then(a.value.cmp(&b.value))); // Expect 1 (vendor) + 2 (cpu brands) + 2 (gpu brands) = 5 keys assert_eq!(keys.len(), 5); assert!(keys.iter().any(|k| k.name == "vendor" && k.value == "SuperVendor")); assert!(keys.iter().any(|k| k.name == "cpu.cpu_brand" && k.value == "Intel")); assert!(keys.iter().any(|k| k.name == "cpu.cpu_brand" && k.value == "AMD")); assert!(keys.iter().any(|k| k.name == "gpu.gpu_brand" && k.value == "NVIDIA")); assert!(keys.iter().any(|k| k.name == "gpu.gpu_brand" && k.value == "AMD")); }