reflex/storage/
model.rs

1//! Storage model types.
2
3use rkyv::{Archive, Deserialize, Serialize};
4
5/// Cached entry persisted to disk.
6///
7/// Stored as `rkyv` bytes (often memory-mapped).
8///
9/// # Example
10/// ```rust
11/// use reflex::CacheEntry;
12///
13/// let entry = CacheEntry {
14///     tenant_id: 1,
15///     context_hash: 42,
16///     timestamp: 0,
17///     embedding: vec![],
18///     payload_blob: vec![],
19/// };
20/// assert_eq!(entry.tenant_id, 1);
21/// ```
22#[derive(Archive, Deserialize, Serialize, Debug, PartialEq, Clone)]
23pub struct CacheEntry {
24    /// Tenant identifier (used for isolation).
25    pub tenant_id: u64,
26    /// Hash of the conversation context.
27    pub context_hash: u64,
28    /// Unix timestamp when cached.
29    pub timestamp: i64,
30    /// Embedding vector bytes (little-endian f16).
31    pub embedding: Vec<u8>,
32    /// Encoded response payload bytes.
33    pub payload_blob: Vec<u8>,
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use rkyv::rancor::Error;
40    use rkyv::{access, from_bytes, to_bytes};
41
42    const TEST_PAYLOAD_BYTES: usize = 1024;
43    const SMALL_ENTRY_MAX_SERIALIZED_BYTES: usize = 1024;
44
45    fn create_test_entry() -> CacheEntry {
46        CacheEntry {
47            tenant_id: 12345678901234567890_u64,
48            context_hash: 9876543210987654321_u64,
49            timestamp: 1702500000_i64,
50            embedding: vec![0x01, 0x02, 0x03, 0x04],
51            payload_blob: vec![0xDE, 0xAD, 0xBE, 0xEF],
52        }
53    }
54
55    fn create_full_embedding_entry() -> CacheEntry {
56        CacheEntry {
57            tenant_id: 1,
58            context_hash: 2,
59            timestamp: 1702500000,
60            embedding: (0..crate::constants::EMBEDDING_F16_BYTES)
61                .map(|i| (i % 256) as u8)
62                .collect(),
63            payload_blob: vec![0x00; TEST_PAYLOAD_BYTES],
64        }
65    }
66
67    #[test]
68    fn test_cache_entry_field_initialization() {
69        let entry = create_test_entry();
70
71        assert_eq!(entry.tenant_id, 12345678901234567890_u64);
72        assert_eq!(entry.context_hash, 9876543210987654321_u64);
73        assert_eq!(entry.timestamp, 1702500000_i64);
74        assert_eq!(entry.embedding, vec![0x01, 0x02, 0x03, 0x04]);
75        assert_eq!(entry.payload_blob, vec![0xDE, 0xAD, 0xBE, 0xEF]);
76    }
77
78    #[test]
79    fn test_cache_entry_debug_trait() {
80        let entry = create_test_entry();
81        let debug_str = format!("{:?}", entry);
82
83        assert!(debug_str.contains("CacheEntry"));
84        assert!(debug_str.contains("tenant_id"));
85        assert!(debug_str.contains("12345678901234567890"));
86    }
87
88    #[test]
89    fn test_cache_entry_clone() {
90        let entry = create_test_entry();
91        let cloned = entry.clone();
92
93        assert_eq!(entry, cloned);
94    }
95
96    #[test]
97    fn test_cache_entry_equality() {
98        let entry1 = create_test_entry();
99        let entry2 = create_test_entry();
100        let entry3 = CacheEntry {
101            tenant_id: 999,
102            ..create_test_entry()
103        };
104
105        assert_eq!(entry1, entry2);
106        assert_ne!(entry1, entry3);
107    }
108
109    #[test]
110    fn test_serialization_roundtrip_basic() {
111        let original = create_test_entry();
112
113        let bytes = to_bytes::<Error>(&original).expect("serialization should succeed");
114
115        let deserialized: CacheEntry =
116            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
117
118        assert_eq!(original, deserialized);
119    }
120
121    #[test]
122    fn test_serialization_roundtrip_full_embedding() {
123        let original = create_full_embedding_entry();
124
125        let bytes = to_bytes::<Error>(&original).expect("serialization should succeed");
126        let deserialized: CacheEntry =
127            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
128
129        assert_eq!(original, deserialized);
130        assert_eq!(
131            deserialized.embedding.len(),
132            crate::constants::EMBEDDING_F16_BYTES
133        );
134    }
135
136    #[test]
137    fn test_archived_zero_copy_access() {
138        let original = create_test_entry();
139
140        let bytes = to_bytes::<Error>(&original).expect("serialization should succeed");
141
142        let archived =
143            access::<ArchivedCacheEntry, Error>(&bytes).expect("archive access should succeed");
144
145        assert_eq!(archived.tenant_id, original.tenant_id);
146        assert_eq!(archived.context_hash, original.context_hash);
147        assert_eq!(archived.timestamp, original.timestamp);
148        assert_eq!(archived.embedding.as_slice(), original.embedding.as_slice());
149        assert_eq!(
150            archived.payload_blob.as_slice(),
151            original.payload_blob.as_slice()
152        );
153    }
154
155    #[test]
156    fn test_archived_embedding_slice_access() {
157        let original = create_full_embedding_entry();
158
159        let bytes = to_bytes::<Error>(&original).expect("serialization should succeed");
160        let archived =
161            access::<ArchivedCacheEntry, Error>(&bytes).expect("archive access should succeed");
162
163        assert_eq!(
164            archived.embedding.len(),
165            crate::constants::EMBEDDING_F16_BYTES
166        );
167        assert_eq!(archived.embedding[0], 0);
168        assert_eq!(archived.embedding[255], 255);
169        assert_eq!(archived.embedding[256], 0);
170    }
171
172    #[test]
173    fn test_empty_vectors() {
174        let entry = CacheEntry {
175            tenant_id: 1,
176            context_hash: 2,
177            timestamp: 3,
178            embedding: vec![],
179            payload_blob: vec![],
180        };
181
182        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
183        let deserialized: CacheEntry =
184            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
185
186        assert!(deserialized.embedding.is_empty());
187        assert!(deserialized.payload_blob.is_empty());
188    }
189
190    #[test]
191    fn test_boundary_values_max() {
192        let entry = CacheEntry {
193            tenant_id: u64::MAX,
194            context_hash: u64::MAX,
195            timestamp: i64::MAX,
196            embedding: vec![0xFF],
197            payload_blob: vec![0xFF],
198        };
199
200        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
201        let deserialized: CacheEntry =
202            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
203
204        assert_eq!(deserialized.tenant_id, u64::MAX);
205        assert_eq!(deserialized.context_hash, u64::MAX);
206        assert_eq!(deserialized.timestamp, i64::MAX);
207    }
208
209    #[test]
210    fn test_boundary_values_min() {
211        let entry = CacheEntry {
212            tenant_id: u64::MIN,
213            context_hash: u64::MIN,
214            timestamp: i64::MIN,
215            embedding: vec![0x00],
216            payload_blob: vec![0x00],
217        };
218
219        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
220        let deserialized: CacheEntry =
221            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
222
223        assert_eq!(deserialized.tenant_id, u64::MIN);
224        assert_eq!(deserialized.context_hash, u64::MIN);
225        assert_eq!(deserialized.timestamp, i64::MIN);
226    }
227
228    #[test]
229    fn test_negative_timestamp() {
230        let entry = CacheEntry {
231            tenant_id: 1,
232            context_hash: 2,
233            timestamp: -1000000000_i64,
234            embedding: vec![],
235            payload_blob: vec![],
236        };
237
238        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
239        let deserialized: CacheEntry =
240            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
241
242        assert_eq!(deserialized.timestamp, -1000000000_i64);
243    }
244
245    #[test]
246    fn test_large_payload_blob() {
247        let large_payload: Vec<u8> = (0..1_000_000).map(|i| (i % 256) as u8).collect();
248
249        let entry = CacheEntry {
250            tenant_id: 1,
251            context_hash: 2,
252            timestamp: 3,
253            embedding: vec![],
254            payload_blob: large_payload.clone(),
255        };
256
257        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
258        let deserialized: CacheEntry =
259            from_bytes::<CacheEntry, Error>(&bytes).expect("deserialization should succeed");
260
261        assert_eq!(deserialized.payload_blob.len(), 1_000_000);
262        assert_eq!(deserialized.payload_blob, large_payload);
263    }
264
265    #[test]
266    fn test_serialized_size_is_reasonable() {
267        let entry = create_test_entry();
268
269        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
270
271        assert!(bytes.len() >= 32);
272        assert!(bytes.len() < SMALL_ENTRY_MAX_SERIALIZED_BYTES);
273    }
274
275    #[test]
276    fn test_full_embedding_serialized_size() {
277        let entry = create_full_embedding_entry();
278
279        let bytes = to_bytes::<Error>(&entry).expect("serialization should succeed");
280
281        assert!(bytes.len() >= crate::constants::EMBEDDING_F16_BYTES + TEST_PAYLOAD_BYTES);
282    }
283}