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}