Skip to main content

ryo_storage/
uuid_storage.rs

1//! File-based UUID persistence implementation.
2//!
3//! Implements the `UuidPersistence` trait from `ryo-analysis` using file storage.
4//! UUID mappings are stored in `.ryo/uuid-mapping.json` within the workspace root.
5
6use ryo_analysis::{UuidPersistence, UuidPersistenceError};
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9
10/// File-based UUID storage implementation.
11///
12/// Stores UUID mappings in `.ryo/uuid-mapping.json` relative to the workspace root.
13///
14/// # Example
15///
16/// ```ignore
17/// use ryo_storage::FileUuidStorage;
18/// use ryo_analysis::UuidPersistence;
19///
20/// let storage = FileUuidStorage::new("/path/to/workspace");
21///
22/// // Load existing mappings
23/// if let Some(mappings) = storage.load() {
24///     println!("Loaded {} mappings", mappings.len());
25/// }
26///
27/// // Save new mappings
28/// let mut mappings = HashMap::new();
29/// mappings.insert("test_crate::Foo".to_string(), "uuid-here".to_string());
30/// storage.save(&mappings)?;
31/// ```
32#[derive(Debug, Clone)]
33pub struct FileUuidStorage {
34    /// Path to the UUID mapping file.
35    file_path: PathBuf,
36}
37
38impl FileUuidStorage {
39    /// Create a new file-based UUID storage.
40    ///
41    /// The UUID mapping file will be at `{workspace_root}/.ryo/uuid-mapping.json`.
42    pub fn new(workspace_root: impl AsRef<Path>) -> Self {
43        let file_path = workspace_root
44            .as_ref()
45            .join(".ryo")
46            .join("uuid-mapping.json");
47        Self { file_path }
48    }
49
50    /// Get the path to the UUID mapping file.
51    pub fn file_path(&self) -> &Path {
52        &self.file_path
53    }
54}
55
56impl UuidPersistence for FileUuidStorage {
57    fn load(&self) -> Option<HashMap<String, String>> {
58        let content = std::fs::read_to_string(&self.file_path).ok()?;
59        serde_json::from_str(&content).ok()
60    }
61
62    fn save(&self, mappings: &HashMap<String, String>) -> Result<(), UuidPersistenceError> {
63        // Create .ryo directory if it doesn't exist
64        if let Some(parent) = self.file_path.parent() {
65            std::fs::create_dir_all(parent)?;
66        }
67
68        let content = serde_json::to_string_pretty(mappings)?;
69        std::fs::write(&self.file_path, content)?;
70        Ok(())
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use tempfile::TempDir;
78
79    #[test]
80    fn test_file_uuid_storage_roundtrip() {
81        let temp_dir = TempDir::new().unwrap();
82        let storage = FileUuidStorage::new(temp_dir.path());
83
84        // Initially empty
85        assert!(storage.load().is_none());
86
87        // Save some mappings
88        let mut mappings = HashMap::new();
89        mappings.insert("test_crate::Foo".to_string(), "uuid-1".to_string());
90        mappings.insert("test_crate::Bar".to_string(), "uuid-2".to_string());
91        storage.save(&mappings).unwrap();
92
93        // Load and verify
94        let loaded = storage.load().unwrap();
95        assert_eq!(loaded.len(), 2);
96        assert_eq!(loaded.get("test_crate::Foo"), Some(&"uuid-1".to_string()));
97        assert_eq!(loaded.get("test_crate::Bar"), Some(&"uuid-2".to_string()));
98    }
99
100    #[test]
101    fn test_file_uuid_storage_creates_directory() {
102        let temp_dir = TempDir::new().unwrap();
103        let storage = FileUuidStorage::new(temp_dir.path());
104
105        // .ryo directory doesn't exist yet
106        assert!(!temp_dir.path().join(".ryo").exists());
107
108        // Save creates the directory
109        let mappings = HashMap::new();
110        storage.save(&mappings).unwrap();
111
112        // Now it exists
113        assert!(temp_dir.path().join(".ryo").exists());
114        assert!(storage.file_path().exists());
115    }
116}