use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
use crate::diff::TextDiff;
use crate::error::DiffError;
fn content_address(content: &str) -> String {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in content.bytes() {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
format!("{hash:016x}")
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VersionAnnotation {
pub prompt_changed: bool,
pub model_changed: bool,
pub temperature_changed: bool,
pub note: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputVersion {
pub id: String,
pub content_address: String,
pub content: String,
pub model: String,
pub created_at: DateTime<Utc>,
pub annotation: VersionAnnotation,
pub parent_id: Option<String>,
}
impl OutputVersion {
pub fn new(
content: impl Into<String>,
model: impl Into<String>,
annotation: VersionAnnotation,
parent_id: Option<String>,
) -> Self {
let content = content.into();
let addr = content_address(&content);
Self {
id: Uuid::new_v4().to_string(),
content_address: addr,
content,
model: model.into(),
created_at: Utc::now(),
annotation,
parent_id,
}
}
}
pub struct VersionStore {
versions: HashMap<String, OutputVersion>,
by_address: HashMap<String, String>,
branches: HashMap<String, String>,
max_output_tokens: usize,
}
impl VersionStore {
pub fn new(max_output_tokens: usize) -> Self {
Self {
versions: HashMap::new(),
by_address: HashMap::new(),
branches: HashMap::new(),
max_output_tokens,
}
}
pub fn store(&mut self, version: OutputVersion) -> Result<String, DiffError> {
let token_estimate = version.content.len() / 4;
if token_estimate > self.max_output_tokens {
return Err(DiffError::OutputTooLarge {
size: token_estimate,
limit: self.max_output_tokens,
});
}
let id = version.id.clone();
self.by_address.insert(version.content_address.clone(), id.clone());
self.versions.insert(id.clone(), version);
Ok(id)
}
pub fn get(&self, id: &str) -> Result<&OutputVersion, DiffError> {
self.versions.get(id).ok_or_else(|| DiffError::VersionNotFound(id.to_string()))
}
pub fn get_by_address(&self, addr: &str) -> Option<&OutputVersion> {
self.by_address.get(addr).and_then(|id| self.versions.get(id))
}
pub fn set_branch(&mut self, branch: impl Into<String>, version_id: impl Into<String>) -> Result<(), DiffError> {
let vid = version_id.into();
if !self.versions.contains_key(&vid) {
return Err(DiffError::VersionNotFound(vid));
}
self.branches.insert(branch.into(), vid);
Ok(())
}
pub fn branch_head(&self, branch: &str) -> Result<&OutputVersion, DiffError> {
let id = self.branches.get(branch)
.ok_or_else(|| DiffError::BranchNotFound(branch.to_string()))?;
self.get(id)
}
pub fn diff_versions(&self, from_id: &str, to_id: &str) -> Result<TextDiff, DiffError> {
let from = self.get(from_id)?;
let to = self.get(to_id)?;
Ok(TextDiff::compute(&from.content, &to.content))
}
pub fn rollback(&self, version_id: &str) -> Result<Option<&OutputVersion>, DiffError> {
let v = self.get(version_id)?;
match &v.parent_id {
Some(pid) => Ok(Some(self.get(pid)?)),
None => Ok(None),
}
}
pub fn lineage(&self, version_id: &str) -> Result<Vec<&OutputVersion>, DiffError> {
let mut chain = Vec::new();
let mut current_id = version_id.to_string();
let mut visited = HashSet::new();
loop {
if visited.contains(¤t_id) {
break;
}
visited.insert(current_id.clone());
let v = self.get(¤t_id)?;
chain.push(v);
match &v.parent_id {
Some(pid) => current_id = pid.clone(),
None => break,
}
}
Ok(chain)
}
pub fn version_count(&self) -> usize { self.versions.len() }
}
#[cfg(test)]
mod tests {
use super::*;
fn v(content: &str, parent: Option<String>) -> OutputVersion {
OutputVersion::new(content, "claude-sonnet-4-6", VersionAnnotation::default(), parent)
}
#[test]
fn test_store_and_retrieve_version_by_id() {
let mut store = VersionStore::new(100_000);
let ver = v("Hello world", None);
let id = store.store(ver).unwrap();
assert!(store.get(&id).is_ok());
}
#[test]
fn test_store_get_nonexistent_id_returns_version_not_found() {
let store = VersionStore::new(100_000);
let err = store.get("nonexistent").unwrap_err();
assert!(matches!(err, DiffError::VersionNotFound(_)));
}
#[test]
fn test_store_output_too_large_returns_error() {
let mut store = VersionStore::new(1);
let large = "a ".repeat(1000);
let err = store.store(v(&large, None)).unwrap_err();
assert!(matches!(err, DiffError::OutputTooLarge { .. }));
}
#[test]
fn test_store_content_address_dedup_maps_same_content() {
let mut store = VersionStore::new(100_000);
let ver1 = v("same content", None);
let addr = ver1.content_address.clone();
store.store(ver1).unwrap();
let ver2 = v("same content", None);
store.store(ver2).unwrap();
assert!(store.get_by_address(&addr).is_some());
}
#[test]
fn test_store_set_branch_ok() {
let mut store = VersionStore::new(100_000);
let id = store.store(v("content", None)).unwrap();
assert!(store.set_branch("main", id).is_ok());
}
#[test]
fn test_store_branch_not_found_returns_error() {
let store = VersionStore::new(100_000);
let err = store.branch_head("nonexistent").unwrap_err();
assert!(matches!(err, DiffError::BranchNotFound(_)));
}
#[test]
fn test_store_set_branch_with_invalid_version_returns_version_not_found() {
let mut store = VersionStore::new(100_000);
let err = store.set_branch("main", "bad-id").unwrap_err();
assert!(matches!(err, DiffError::VersionNotFound(_)));
}
#[test]
fn test_store_diff_versions_identical_content_is_identical() {
let mut store = VersionStore::new(100_000);
let id1 = store.store(v("same text", None)).unwrap();
let id2 = store.store(v("same text", None)).unwrap();
let diff = store.diff_versions(&id1, &id2).unwrap();
assert!(diff.is_identical());
}
#[test]
fn test_store_rollback_returns_parent_version() {
let mut store = VersionStore::new(100_000);
let parent_id = store.store(v("version 1", None)).unwrap();
let child = v("version 2", Some(parent_id.clone()));
let child_id = store.store(child).unwrap();
let parent = store.rollback(&child_id).unwrap().unwrap();
assert_eq!(parent.id, parent_id);
}
#[test]
fn test_store_rollback_root_returns_none() {
let mut store = VersionStore::new(100_000);
let id = store.store(v("root version", None)).unwrap();
let result = store.rollback(&id).unwrap();
assert!(result.is_none());
}
#[test]
fn test_store_lineage_three_generations_length() {
let mut store = VersionStore::new(100_000);
let id1 = store.store(v("v1", None)).unwrap();
let id2 = store.store(v("v2", Some(id1.clone()))).unwrap();
let id3 = store.store(v("v3", Some(id2.clone()))).unwrap();
let lineage = store.lineage(&id3).unwrap();
assert_eq!(lineage.len(), 3);
}
#[test]
fn test_store_version_count_increments() {
let mut store = VersionStore::new(100_000);
assert_eq!(store.version_count(), 0);
store.store(v("a", None)).unwrap();
assert_eq!(store.version_count(), 1);
}
#[test]
fn test_store_branch_head_returns_correct_version() {
let mut store = VersionStore::new(100_000);
let id = store.store(v("content", None)).unwrap();
store.set_branch("main", id.clone()).unwrap();
let head = store.branch_head("main").unwrap();
assert_eq!(head.id, id);
}
}