1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use ucm_core::{Document, DocumentVersion, Error, Result};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct SnapshotId(pub String);
11
12impl SnapshotId {
13 pub fn new(name: impl Into<String>) -> Self {
14 Self(name.into())
15 }
16}
17
18impl std::fmt::Display for SnapshotId {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(f, "{}", self.0)
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Snapshot {
27 pub id: SnapshotId,
29 pub description: Option<String>,
31 pub created_at: DateTime<Utc>,
33 pub document_version: DocumentVersion,
35 pub data: SnapshotData,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub enum SnapshotData {
42 Full(SerializedDocument),
44 Delta {
46 base: SnapshotId,
47 changes: Vec<SnapshotChange>,
48 },
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SerializedDocument {
54 pub json: String,
56}
57
58impl SerializedDocument {
59 pub fn from_document(doc: &Document) -> Result<Self> {
60 let serializable = SerializableDocument::from(doc);
63 let json = serde_json::to_string(&serializable)
64 .map_err(|e| Error::Internal(format!("Failed to serialize document: {}", e)))?;
65 Ok(Self { json })
66 }
67
68 pub fn to_document(&self) -> Result<Document> {
69 let serializable: SerializableDocument = serde_json::from_str(&self.json)
70 .map_err(|e| Error::Internal(format!("Failed to deserialize document: {}", e)))?;
71 Ok(serializable.into())
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77struct SerializableDocument {
78 id: String,
79 root: String,
80 structure: HashMap<String, Vec<String>>,
81 blocks: HashMap<String, serde_json::Value>,
82 metadata: serde_json::Value,
83 version: DocumentVersion,
84}
85
86impl From<&Document> for SerializableDocument {
87 fn from(doc: &Document) -> Self {
88 let structure: HashMap<String, Vec<String>> = doc
89 .structure
90 .iter()
91 .map(|(k, v)| (k.to_string(), v.iter().map(|id| id.to_string()).collect()))
92 .collect();
93
94 let blocks: HashMap<String, serde_json::Value> = doc
95 .blocks
96 .iter()
97 .map(|(k, v)| (k.to_string(), serde_json::to_value(v).unwrap_or_default()))
98 .collect();
99
100 Self {
101 id: doc.id.0.clone(),
102 root: doc.root.to_string(),
103 structure,
104 blocks,
105 metadata: serde_json::to_value(&doc.metadata).unwrap_or_default(),
106 version: doc.version.clone(),
107 }
108 }
109}
110
111impl From<SerializableDocument> for Document {
112 fn from(s: SerializableDocument) -> Self {
113 use ucm_core::{Block, BlockId, DocumentId, DocumentMetadata};
114
115 let root: BlockId = s.root.parse().unwrap_or_else(|_| BlockId::root());
116
117 let structure: HashMap<BlockId, Vec<BlockId>> = s
118 .structure
119 .into_iter()
120 .filter_map(|(k, v)| {
121 let key: BlockId = k.parse().ok()?;
122 let values: Vec<BlockId> = v.into_iter().filter_map(|id| id.parse().ok()).collect();
123 Some((key, values))
124 })
125 .collect();
126
127 let blocks: HashMap<BlockId, Block> = s
128 .blocks
129 .into_iter()
130 .filter_map(|(k, v)| {
131 let key: BlockId = k.parse().ok()?;
132 let block: Block = serde_json::from_value(v).ok()?;
133 Some((key, block))
134 })
135 .collect();
136
137 let metadata: DocumentMetadata = serde_json::from_value(s.metadata).unwrap_or_default();
138
139 let mut doc = Document::new(DocumentId::new(s.id));
140 doc.root = root;
141 doc.structure = structure;
142 doc.blocks = blocks;
143 doc.metadata = metadata;
144 doc.version = s.version;
145 doc.rebuild_indices();
146 doc
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum SnapshotChange {
153 AddBlock {
154 id: String,
155 block: serde_json::Value,
156 },
157 RemoveBlock {
158 id: String,
159 },
160 ModifyBlock {
161 id: String,
162 block: serde_json::Value,
163 },
164 UpdateStructure {
165 parent: String,
166 children: Vec<String>,
167 },
168}
169
170#[derive(Debug, Default)]
172pub struct SnapshotManager {
173 snapshots: HashMap<SnapshotId, Snapshot>,
174 max_snapshots: usize,
175}
176
177impl SnapshotManager {
178 pub fn new() -> Self {
179 Self {
180 snapshots: HashMap::new(),
181 max_snapshots: 100,
182 }
183 }
184
185 pub fn with_max_snapshots(max: usize) -> Self {
186 Self {
187 snapshots: HashMap::new(),
188 max_snapshots: max,
189 }
190 }
191
192 pub fn create(
194 &mut self,
195 name: impl Into<String>,
196 doc: &Document,
197 description: Option<String>,
198 ) -> Result<SnapshotId> {
199 let id = SnapshotId::new(name);
200
201 if self.snapshots.len() >= self.max_snapshots {
203 self.evict_oldest();
204 }
205
206 let data = SnapshotData::Full(SerializedDocument::from_document(doc)?);
207
208 let snapshot = Snapshot {
209 id: id.clone(),
210 description,
211 created_at: Utc::now(),
212 document_version: doc.version.clone(),
213 data,
214 };
215
216 self.snapshots.insert(id.clone(), snapshot);
217 Ok(id)
218 }
219
220 pub fn restore(&self, name: &str) -> Result<Document> {
222 let id = SnapshotId::new(name);
223 let snapshot = self
224 .snapshots
225 .get(&id)
226 .ok_or_else(|| Error::Internal(format!("Snapshot '{}' not found", name)))?;
227
228 match &snapshot.data {
229 SnapshotData::Full(serialized) => serialized.to_document(),
230 SnapshotData::Delta { .. } => {
231 Err(Error::Internal("Delta snapshots not yet supported".into()))
233 }
234 }
235 }
236
237 pub fn get(&self, name: &str) -> Option<&Snapshot> {
239 self.snapshots.get(&SnapshotId::new(name))
240 }
241
242 pub fn list(&self) -> Vec<&Snapshot> {
244 let mut snapshots: Vec<_> = self.snapshots.values().collect();
245 snapshots.sort_by(|a, b| b.created_at.cmp(&a.created_at));
246 snapshots
247 }
248
249 pub fn delete(&mut self, name: &str) -> bool {
251 self.snapshots.remove(&SnapshotId::new(name)).is_some()
252 }
253
254 pub fn exists(&self, name: &str) -> bool {
256 self.snapshots.contains_key(&SnapshotId::new(name))
257 }
258
259 pub fn count(&self) -> usize {
261 self.snapshots.len()
262 }
263
264 fn evict_oldest(&mut self) {
266 if let Some(oldest) = self
267 .snapshots
268 .values()
269 .min_by_key(|s| s.created_at)
270 .map(|s| s.id.clone())
271 {
272 self.snapshots.remove(&oldest);
273 }
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use ucm_core::{Block, Content, DocumentId};
281
282 #[test]
283 fn test_snapshot_create_restore() {
284 let mut mgr = SnapshotManager::new();
285 let mut doc = Document::new(DocumentId::new("test"));
286
287 let root = doc.root;
288 doc.add_block(Block::new(Content::text("Hello"), Some("intro")), &root)
289 .unwrap();
290
291 mgr.create("v1", &doc, Some("First version".into()))
292 .unwrap();
293
294 let restored = mgr.restore("v1").unwrap();
295 assert_eq!(restored.block_count(), doc.block_count());
296 }
297
298 #[test]
299 fn test_snapshot_list() {
300 let mut mgr = SnapshotManager::new();
301 let doc = Document::create();
302
303 mgr.create("v1", &doc, None).unwrap();
304 mgr.create("v2", &doc, None).unwrap();
305 mgr.create("v3", &doc, None).unwrap();
306
307 assert_eq!(mgr.count(), 3);
308 assert_eq!(mgr.list().len(), 3);
309 }
310
311 #[test]
312 fn test_snapshot_delete() {
313 let mut mgr = SnapshotManager::new();
314 let doc = Document::create();
315
316 mgr.create("v1", &doc, None).unwrap();
317 assert!(mgr.exists("v1"));
318
319 mgr.delete("v1");
320 assert!(!mgr.exists("v1"));
321 }
322
323 #[test]
324 fn test_snapshot_eviction() {
325 let mut mgr = SnapshotManager::with_max_snapshots(2);
326 let doc = Document::create();
327
328 mgr.create("v1", &doc, None).unwrap();
329 mgr.create("v2", &doc, None).unwrap();
330 mgr.create("v3", &doc, None).unwrap();
331
332 assert_eq!(mgr.count(), 2);
333 assert!(!mgr.exists("v1")); }
335}