Skip to main content

forge_reasoning/
export_import.rs

1//! Export and Import functionality for checkpoints
2//!
3//! Provides serialization to/from JSON for backup and migration.
4
5use serde::{Deserialize, Serialize};
6
7use crate::checkpoint::{SessionId, TemporalCheckpoint};
8use crate::errors::{Result, StorageError};
9use crate::thread_safe::ThreadSafeStorage;
10
11/// Export format for a session's checkpoints
12#[derive(Serialize, Deserialize)]
13pub struct SessionExport {
14    pub version: String,
15    pub session_id: SessionId,
16    pub exported_at: chrono::DateTime<chrono::Utc>,
17    pub checkpoints: Vec<TemporalCheckpoint>,
18}
19
20/// Exports checkpoints to various formats
21pub struct CheckpointExporter {
22    storage: ThreadSafeStorage,
23}
24
25impl CheckpointExporter {
26    /// Create a new exporter for the given storage
27    pub fn new(storage: ThreadSafeStorage) -> Self {
28        Self { storage }
29    }
30
31    /// Export all checkpoints for a session as JSON
32    pub fn export_session(&self, session_id: &SessionId) -> Result<String> {
33        // Get all checkpoints for the session
34        let summaries = self.storage.list_by_session(*session_id)?;
35        
36        let mut checkpoints = Vec::new();
37        for summary in summaries {
38            if let Ok(cp) = self.storage.get(summary.id) {
39                checkpoints.push(cp);
40            }
41        }
42        
43        let export = SessionExport {
44            version: "1.0".to_string(),
45            session_id: *session_id,
46            exported_at: chrono::Utc::now(),
47            checkpoints,
48        };
49        
50        serde_json::to_string_pretty(&export)
51            .map_err(|e| StorageError::StoreFailed(format!("Export serialization failed: {}", e)).into())
52    }
53
54    /// Export to a file
55    pub fn export_session_to_file(&self, session_id: &SessionId, path: &std::path::Path) -> Result<()> {
56        let json = self.export_session(session_id)?;
57        std::fs::write(path, json)
58            .map_err(|e| StorageError::StoreFailed(format!("Failed to write export file: {}", e)).into())
59    }
60}
61
62/// Imports checkpoints from various formats
63pub struct CheckpointImporter {
64    storage: ThreadSafeStorage,
65}
66
67impl CheckpointImporter {
68    /// Create a new importer for the given storage
69    pub fn new(storage: ThreadSafeStorage) -> Self {
70        Self { storage }
71    }
72
73    /// Import checkpoints from JSON string
74    pub fn import_session(&self, json: &str) -> Result<usize> {
75        let export: SessionExport = serde_json::from_str(json)
76            .map_err(|e| StorageError::RetrieveFailed(format!("Import deserialization failed: {}", e)))?;
77        
78        let mut count = 0;
79        for checkpoint in export.checkpoints {
80            self.storage.store(&checkpoint)?;
81            count += 1;
82        }
83        
84        Ok(count)
85    }
86
87    /// Import from a file
88    pub fn import_session_from_file(&self, path: &std::path::Path) -> Result<usize> {
89        let json = std::fs::read_to_string(path)
90            .map_err(|e| StorageError::RetrieveFailed(format!("Failed to read import file: {}", e)))?;
91        self.import_session(&json)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::SqliteGraphStorage;
99
100    #[test]
101    fn test_export_import_roundtrip() {
102        let storage = ThreadSafeStorage::new(SqliteGraphStorage::in_memory().unwrap());
103        let session_id = SessionId::new();
104        
105        // Export (will be empty but should work)
106        let exporter = CheckpointExporter::new(storage.clone());
107        let json = exporter.export_session(&session_id).unwrap();
108        
109        // Should be valid JSON
110        assert!(json.contains("version"));
111        assert!(json.contains("session_id"));
112        
113        // Import to new storage
114        let new_storage = ThreadSafeStorage::new(SqliteGraphStorage::in_memory().unwrap());
115        let importer = CheckpointImporter::new(new_storage);
116        let count = importer.import_session(&json).unwrap();
117        
118        // Empty export imports 0 checkpoints
119        assert_eq!(count, 0);
120    }
121}