use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
pub struct StateRef(String);
impl StateRef {
pub fn from_content(content: &str) -> Self {
use std::hash::Hash;
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
let hash = hasher.finish();
Self(format!("{:016x}", hash))
}
pub fn from_hash(hash: String) -> Self {
Self(hash)
}
pub fn hash(&self) -> &str {
&self.0
}
pub fn short(&self) -> &str {
&self.0[..8.min(self.0.len())]
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for StateRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StateRef({}...)", self.short())
}
}
impl fmt::Display for StateRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.short())
}
}
impl Hash for StateRef {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
pub trait StateStore: Send + Sync {
fn store(&mut self, content: &str) -> StateRef;
fn load(&self, state_ref: &StateRef) -> Option<String>;
fn exists(&self, state_ref: &StateRef) -> bool {
self.load(state_ref).is_some()
}
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Default)]
pub struct InMemoryStateStore {
states: HashMap<StateRef, String>,
}
impl InMemoryStateStore {
pub fn new() -> Self {
Self::default()
}
pub fn refs(&self) -> impl Iterator<Item = &StateRef> {
self.states.keys()
}
pub fn clear(&mut self) {
self.states.clear();
}
pub fn size_bytes(&self) -> usize {
self.states.values().map(|s| s.len()).sum()
}
}
impl StateStore for InMemoryStateStore {
fn store(&mut self, content: &str) -> StateRef {
let state_ref = StateRef::from_content(content);
if !self.states.contains_key(&state_ref) {
self.states.insert(state_ref.clone(), content.to_string());
}
state_ref
}
fn load(&self, state_ref: &StateRef) -> Option<String> {
self.states.get(state_ref).cloned()
}
fn exists(&self, state_ref: &StateRef) -> bool {
self.states.contains_key(state_ref)
}
fn len(&self) -> usize {
self.states.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileStateSnapshot {
pub path: String,
pub state_ref: StateRef,
}
impl FileStateSnapshot {
pub fn new(path: impl Into<String>, state_ref: StateRef) -> Self {
Self {
path: path.into(),
state_ref,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WorkspaceSnapshot {
pub files: HashMap<String, StateRef>,
pub name: Option<String>,
pub timestamp_ms: u64,
}
impl WorkspaceSnapshot {
pub fn new() -> Self {
Self::default()
}
pub fn named(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..Default::default()
}
}
pub fn add_file(&mut self, path: impl Into<String>, state_ref: StateRef) {
self.files.insert(path.into(), state_ref);
}
pub fn get_file(&self, path: &str) -> Option<&StateRef> {
self.files.get(path)
}
pub fn file_count(&self) -> usize {
self.files.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_ref_from_content() {
let content = "fn main() {}";
let ref1 = StateRef::from_content(content);
let ref2 = StateRef::from_content(content);
assert_eq!(ref1, ref2);
let ref3 = StateRef::from_content("fn main() { println!(); }");
assert_ne!(ref1, ref3);
}
#[test]
fn test_state_ref_display() {
let content = "fn main() {}";
let state_ref = StateRef::from_content(content);
assert_eq!(state_ref.short().len(), 8);
assert!(format!("{}", state_ref).len() == 8);
assert!(format!("{:?}", state_ref).contains("..."));
}
#[test]
fn test_in_memory_store_roundtrip() {
let mut store = InMemoryStateStore::new();
let content = "fn main() {}";
let state_ref = store.store(content);
let loaded = store.load(&state_ref).unwrap();
assert_eq!(loaded, content);
}
#[test]
fn test_in_memory_store_deduplication() {
let mut store = InMemoryStateStore::new();
let content = "fn main() {}";
store.store(content);
store.store(content);
store.store(content);
assert_eq!(store.len(), 1);
}
#[test]
fn test_in_memory_store_multiple_files() {
let mut store = InMemoryStateStore::new();
let ref1 = store.store("content 1");
let ref2 = store.store("content 2");
let ref3 = store.store("content 3");
assert_eq!(store.len(), 3);
assert_ne!(ref1, ref2);
assert_ne!(ref2, ref3);
assert_eq!(store.load(&ref1), Some("content 1".to_string()));
assert_eq!(store.load(&ref2), Some("content 2".to_string()));
assert_eq!(store.load(&ref3), Some("content 3".to_string()));
}
#[test]
fn test_workspace_snapshot() {
let mut store = InMemoryStateStore::new();
let mut snapshot = WorkspaceSnapshot::named("v1.0");
let ref1 = store.store("fn main() {}");
let ref2 = store.store("mod lib;");
snapshot.add_file("src/main.rs", ref1.clone());
snapshot.add_file("src/lib.rs", ref2.clone());
assert_eq!(snapshot.file_count(), 2);
assert_eq!(snapshot.get_file("src/main.rs"), Some(&ref1));
assert_eq!(snapshot.get_file("src/lib.rs"), Some(&ref2));
}
#[test]
fn test_state_ref_serialization() {
let state_ref = StateRef::from_content("test content");
let json = serde_json::to_string(&state_ref).unwrap();
let deserialized: StateRef = serde_json::from_str(&json).unwrap();
assert_eq!(state_ref, deserialized);
}
}