cache_kit/
entity.rs

1//! Core entity trait that all cached entities must implement.
2
3use crate::error::Result;
4use serde::{Deserialize, Serialize};
5use std::fmt::Display;
6use std::hash::Hash;
7
8/// Trait that all entities stored in cache must implement.
9///
10/// # Example
11///
12/// ```
13/// use serde::{Deserialize, Serialize};
14/// use cache_kit::CacheEntity;
15///
16/// #[derive(Clone, Serialize, Deserialize)]
17/// pub struct Employment {
18///     pub id: String,
19///     pub employer_name: String,
20/// }
21///
22/// impl CacheEntity for Employment {
23///     type Key = String;
24///
25///     fn cache_key(&self) -> Self::Key {
26///         self.id.clone()
27///     }
28///
29///     fn cache_prefix() -> &'static str {
30///         "employment"
31///     }
32/// }
33/// ```
34pub trait CacheEntity: Send + Sync + Serialize + for<'de> Deserialize<'de> + Clone {
35    /// Type of the entity's key/ID (typically String or UUID)
36    type Key: Display + Clone + Send + Sync + Eq + Hash + 'static;
37
38    /// Return the entity's unique cache key.
39    ///
40    /// Called to extract the key from the entity itself.
41    /// Example: `Employment.id` → `"emp_12345"`
42    fn cache_key(&self) -> Self::Key;
43
44    /// Return the cache prefix for this entity type.
45    ///
46    /// Used to namespace cache keys. Example: "employment", "borrower"
47    /// Final cache key format: `"{prefix}:{key}"`
48    fn cache_prefix() -> &'static str;
49
50    /// Serialize entity for cache storage.
51    ///
52    /// Uses Postcard with versioned envelopes for all cache storage.
53    /// This method is NOT overridable to ensure consistency across all entities.
54    ///
55    /// # Format
56    ///
57    /// ```text
58    /// [MAGIC: 4 bytes] [VERSION: 4 bytes] [POSTCARD PAYLOAD]
59    /// ```
60    ///
61    /// # Performance
62    ///
63    /// - 10-15x faster than JSON
64    /// - 60% smaller payloads
65    ///
66    /// See `crate::serialization` for implementation details.
67    fn serialize_for_cache(&self) -> Result<Vec<u8>> {
68        crate::serialization::serialize_for_cache(self)
69    }
70
71    /// Deserialize entity from cache storage.
72    ///
73    /// Validates magic header and schema version before deserializing.
74    /// This method is NOT overridable to ensure consistency across all entities.
75    ///
76    /// # Validation
77    ///
78    /// - Magic must be b"CKIT"
79    /// - Version must match current schema version
80    /// - Postcard deserialization must succeed
81    ///
82    /// # Errors
83    ///
84    /// - `Error::InvalidCacheEntry`: Bad magic or corrupted envelope
85    /// - `Error::VersionMismatch`: Schema version changed
86    /// - `Error::DeserializationError`: Corrupted payload
87    ///
88    /// See `crate::serialization` for implementation details.
89    fn deserialize_from_cache(bytes: &[u8]) -> Result<Self> {
90        crate::serialization::deserialize_from_cache(bytes)
91    }
92
93    /// Optional: Validate entity after deserialization.
94    ///
95    /// Called after loading from cache. Use to ensure consistency.
96    fn validate(&self) -> Result<()> {
97        Ok(())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use serde::{Deserialize, Serialize};
105
106    #[derive(Clone, Serialize, Deserialize)]
107    struct TestEntity {
108        id: String,
109        value: String,
110    }
111
112    impl CacheEntity for TestEntity {
113        type Key = String;
114
115        fn cache_key(&self) -> Self::Key {
116            self.id.clone()
117        }
118
119        fn cache_prefix() -> &'static str {
120            "test"
121        }
122    }
123
124    #[test]
125    fn test_serialize_deserialize() {
126        let entity = TestEntity {
127            id: "test_1".to_string(),
128            value: "data".to_string(),
129        };
130
131        let bytes = entity.serialize_for_cache().unwrap();
132        let deserialized = TestEntity::deserialize_from_cache(&bytes).unwrap();
133
134        assert_eq!(entity.id, deserialized.id);
135        assert_eq!(entity.value, deserialized.value);
136    }
137
138    #[test]
139    fn test_cache_key_generation() {
140        let entity = TestEntity {
141            id: "entity_123".to_string(),
142            value: "test".to_string(),
143        };
144
145        assert_eq!(entity.cache_key(), "entity_123");
146        assert_eq!(TestEntity::cache_prefix(), "test");
147    }
148}