envelope_cli/export/
yaml.rs1use crate::error::EnvelopeResult;
6use crate::export::json::FullExport;
7use crate::storage::Storage;
8use std::io::Write;
9
10pub fn export_full_yaml<W: Write>(storage: &Storage, writer: &mut W) -> EnvelopeResult<()> {
12 let export = FullExport::from_storage(storage)?;
13
14 writeln!(writer, "# EnvelopeCLI Full Database Export")
16 .map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
17 writeln!(writer, "# Generated: {}", export.exported_at)
18 .map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
19 writeln!(writer, "# App Version: {}", export.app_version)
20 .map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
21 writeln!(writer, "#").map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
22 writeln!(
23 writer,
24 "# This file can be used to restore your budget data."
25 )
26 .map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
27 writeln!(
28 writer,
29 "# Keep it secure - it contains all your financial data."
30 )
31 .map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
32 writeln!(writer).map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
33
34 serde_yaml::to_writer(writer, &export)
36 .map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
37
38 Ok(())
39}
40
41pub fn import_from_yaml(yaml_str: &str) -> EnvelopeResult<FullExport> {
43 let export: FullExport = serde_yaml::from_str(yaml_str)
44 .map_err(|e| crate::error::EnvelopeError::Import(e.to_string()))?;
45
46 export
48 .validate()
49 .map_err(crate::error::EnvelopeError::Import)?;
50
51 Ok(export)
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::config::paths::EnvelopePaths;
58 use crate::models::{Account, AccountType, Category, CategoryGroup};
59 use tempfile::TempDir;
60
61 fn create_test_storage() -> (TempDir, Storage) {
62 let temp_dir = TempDir::new().unwrap();
63 let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
64 let mut storage = Storage::new(paths).unwrap();
65 storage.load_all().unwrap();
66 (temp_dir, storage)
67 }
68
69 #[test]
70 fn test_yaml_export() {
71 let (_temp_dir, storage) = create_test_storage();
72
73 let account = Account::new("Checking", AccountType::Checking);
75 storage.accounts.upsert(account).unwrap();
76 storage.accounts.save().unwrap();
77
78 let group = CategoryGroup::new("Test");
79 storage.categories.upsert_group(group.clone()).unwrap();
80 let cat = Category::new("Groceries", group.id);
81 storage.categories.upsert_category(cat).unwrap();
82 storage.categories.save().unwrap();
83
84 let mut yaml_output = Vec::new();
86 export_full_yaml(&storage, &mut yaml_output).unwrap();
87
88 let yaml_string = String::from_utf8(yaml_output).unwrap();
89
90 assert!(yaml_string.contains("# EnvelopeCLI Full Database Export"));
92
93 assert!(yaml_string.contains("Checking"));
95 assert!(yaml_string.contains("Groceries"));
96 }
97
98 #[test]
99 fn test_yaml_roundtrip() {
100 let (_temp_dir, storage) = create_test_storage();
101
102 let account = Account::new("Checking", AccountType::Checking);
104 storage.accounts.upsert(account).unwrap();
105 storage.accounts.save().unwrap();
106
107 let mut yaml_output = Vec::new();
109 export_full_yaml(&storage, &mut yaml_output).unwrap();
110
111 let yaml_string = String::from_utf8(yaml_output).unwrap();
112
113 let yaml_content: String = yaml_string
115 .lines()
116 .filter(|line| !line.starts_with('#'))
117 .collect::<Vec<_>>()
118 .join("\n");
119
120 let imported = import_from_yaml(&yaml_content).unwrap();
122
123 assert_eq!(imported.accounts.len(), 1);
124 assert_eq!(imported.accounts[0].name, "Checking");
125 }
126}