ryo_storage/storage/
state.rs1use serde::{Deserialize, Serialize};
38use std::collections::hash_map::DefaultHasher;
39use std::collections::HashMap;
40use std::fmt;
41use std::hash::{Hash, Hasher};
42
43#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
57pub struct StateRef(String);
58
59impl StateRef {
60 pub fn from_content(content: &str) -> Self {
65 use std::hash::Hash;
66 let mut hasher = DefaultHasher::new();
67 content.hash(&mut hasher);
68 let hash = hasher.finish();
69 Self(format!("{:016x}", hash))
70 }
71
72 pub fn from_hash(hash: String) -> Self {
76 Self(hash)
77 }
78
79 pub fn hash(&self) -> &str {
81 &self.0
82 }
83
84 pub fn short(&self) -> &str {
86 &self.0[..8.min(self.0.len())]
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.0.is_empty()
92 }
93}
94
95impl fmt::Debug for StateRef {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "StateRef({}...)", self.short())
98 }
99}
100
101impl fmt::Display for StateRef {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "{}", self.short())
104 }
105}
106
107impl Hash for StateRef {
108 fn hash<H: Hasher>(&self, state: &mut H) {
109 self.0.hash(state);
110 }
111}
112
113pub trait StateStore: Send + Sync {
124 fn store(&mut self, content: &str) -> StateRef;
129
130 fn load(&self, state_ref: &StateRef) -> Option<String>;
134
135 fn exists(&self, state_ref: &StateRef) -> bool {
137 self.load(state_ref).is_some()
138 }
139
140 fn len(&self) -> usize;
142
143 fn is_empty(&self) -> bool {
145 self.len() == 0
146 }
147}
148
149#[derive(Debug, Default)]
162pub struct InMemoryStateStore {
163 states: HashMap<StateRef, String>,
164}
165
166impl InMemoryStateStore {
167 pub fn new() -> Self {
169 Self::default()
170 }
171
172 pub fn refs(&self) -> impl Iterator<Item = &StateRef> {
174 self.states.keys()
175 }
176
177 pub fn clear(&mut self) {
179 self.states.clear();
180 }
181
182 pub fn size_bytes(&self) -> usize {
184 self.states.values().map(|s| s.len()).sum()
185 }
186}
187
188impl StateStore for InMemoryStateStore {
189 fn store(&mut self, content: &str) -> StateRef {
190 let state_ref = StateRef::from_content(content);
191
192 if !self.states.contains_key(&state_ref) {
194 self.states.insert(state_ref.clone(), content.to_string());
195 }
196
197 state_ref
198 }
199
200 fn load(&self, state_ref: &StateRef) -> Option<String> {
201 self.states.get(state_ref).cloned()
202 }
203
204 fn exists(&self, state_ref: &StateRef) -> bool {
205 self.states.contains_key(state_ref)
206 }
207
208 fn len(&self) -> usize {
209 self.states.len()
210 }
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct FileStateSnapshot {
222 pub path: String,
224 pub state_ref: StateRef,
226}
227
228impl FileStateSnapshot {
229 pub fn new(path: impl Into<String>, state_ref: StateRef) -> Self {
231 Self {
232 path: path.into(),
233 state_ref,
234 }
235 }
236}
237
238#[derive(Debug, Clone, Default, Serialize, Deserialize)]
240pub struct WorkspaceSnapshot {
241 pub files: HashMap<String, StateRef>,
243 pub name: Option<String>,
245 pub timestamp_ms: u64,
247}
248
249impl WorkspaceSnapshot {
250 pub fn new() -> Self {
252 Self::default()
253 }
254
255 pub fn named(name: impl Into<String>) -> Self {
257 Self {
258 name: Some(name.into()),
259 ..Default::default()
260 }
261 }
262
263 pub fn add_file(&mut self, path: impl Into<String>, state_ref: StateRef) {
265 self.files.insert(path.into(), state_ref);
266 }
267
268 pub fn get_file(&self, path: &str) -> Option<&StateRef> {
270 self.files.get(path)
271 }
272
273 pub fn file_count(&self) -> usize {
275 self.files.len()
276 }
277}
278
279#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_state_ref_from_content() {
289 let content = "fn main() {}";
290 let ref1 = StateRef::from_content(content);
291 let ref2 = StateRef::from_content(content);
292
293 assert_eq!(ref1, ref2);
295
296 let ref3 = StateRef::from_content("fn main() { println!(); }");
298 assert_ne!(ref1, ref3);
299 }
300
301 #[test]
302 fn test_state_ref_display() {
303 let content = "fn main() {}";
304 let state_ref = StateRef::from_content(content);
305
306 assert_eq!(state_ref.short().len(), 8);
308 assert!(format!("{}", state_ref).len() == 8);
309 assert!(format!("{:?}", state_ref).contains("..."));
310 }
311
312 #[test]
313 fn test_in_memory_store_roundtrip() {
314 let mut store = InMemoryStateStore::new();
315
316 let content = "fn main() {}";
317 let state_ref = store.store(content);
318
319 let loaded = store.load(&state_ref).unwrap();
320 assert_eq!(loaded, content);
321 }
322
323 #[test]
324 fn test_in_memory_store_deduplication() {
325 let mut store = InMemoryStateStore::new();
326
327 let content = "fn main() {}";
328 store.store(content);
329 store.store(content);
330 store.store(content);
331
332 assert_eq!(store.len(), 1);
334 }
335
336 #[test]
337 fn test_in_memory_store_multiple_files() {
338 let mut store = InMemoryStateStore::new();
339
340 let ref1 = store.store("content 1");
341 let ref2 = store.store("content 2");
342 let ref3 = store.store("content 3");
343
344 assert_eq!(store.len(), 3);
345 assert_ne!(ref1, ref2);
346 assert_ne!(ref2, ref3);
347
348 assert_eq!(store.load(&ref1), Some("content 1".to_string()));
349 assert_eq!(store.load(&ref2), Some("content 2".to_string()));
350 assert_eq!(store.load(&ref3), Some("content 3".to_string()));
351 }
352
353 #[test]
354 fn test_workspace_snapshot() {
355 let mut store = InMemoryStateStore::new();
356 let mut snapshot = WorkspaceSnapshot::named("v1.0");
357
358 let ref1 = store.store("fn main() {}");
359 let ref2 = store.store("mod lib;");
360
361 snapshot.add_file("src/main.rs", ref1.clone());
362 snapshot.add_file("src/lib.rs", ref2.clone());
363
364 assert_eq!(snapshot.file_count(), 2);
365 assert_eq!(snapshot.get_file("src/main.rs"), Some(&ref1));
366 assert_eq!(snapshot.get_file("src/lib.rs"), Some(&ref2));
367 }
368
369 #[test]
370 fn test_state_ref_serialization() {
371 let state_ref = StateRef::from_content("test content");
372
373 let json = serde_json::to_string(&state_ref).unwrap();
374 let deserialized: StateRef = serde_json::from_str(&json).unwrap();
375
376 assert_eq!(state_ref, deserialized);
377 }
378}