use crate::error::EnvelopeResult;
use crate::export::json::FullExport;
use crate::storage::Storage;
use std::io::Write;
pub fn export_full_yaml<W: Write>(storage: &Storage, writer: &mut W) -> EnvelopeResult<()> {
let export = FullExport::from_storage(storage)?;
writeln!(writer, "# EnvelopeCLI Full Database Export")
.map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
writeln!(writer, "# Generated: {}", export.exported_at)
.map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
writeln!(writer, "# App Version: {}", export.app_version)
.map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
writeln!(writer, "#").map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
writeln!(
writer,
"# This file can be used to restore your budget data."
)
.map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
writeln!(
writer,
"# Keep it secure - it contains all your financial data."
)
.map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
writeln!(writer).map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
serde_yaml::to_writer(writer, &export)
.map_err(|e| crate::error::EnvelopeError::Export(e.to_string()))?;
Ok(())
}
pub fn import_from_yaml(yaml_str: &str) -> EnvelopeResult<FullExport> {
let export: FullExport = serde_yaml::from_str(yaml_str)
.map_err(|e| crate::error::EnvelopeError::Import(e.to_string()))?;
export
.validate()
.map_err(crate::error::EnvelopeError::Import)?;
Ok(export)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::paths::EnvelopePaths;
use crate::models::{Account, AccountType, Category, CategoryGroup};
use tempfile::TempDir;
fn create_test_storage() -> (TempDir, Storage) {
let temp_dir = TempDir::new().unwrap();
let paths = EnvelopePaths::with_base_dir(temp_dir.path().to_path_buf());
let mut storage = Storage::new(paths).unwrap();
storage.load_all().unwrap();
(temp_dir, storage)
}
#[test]
fn test_yaml_export() {
let (_temp_dir, storage) = create_test_storage();
let account = Account::new("Checking", AccountType::Checking);
storage.accounts.upsert(account).unwrap();
storage.accounts.save().unwrap();
let group = CategoryGroup::new("Test");
storage.categories.upsert_group(group.clone()).unwrap();
let cat = Category::new("Groceries", group.id);
storage.categories.upsert_category(cat).unwrap();
storage.categories.save().unwrap();
let mut yaml_output = Vec::new();
export_full_yaml(&storage, &mut yaml_output).unwrap();
let yaml_string = String::from_utf8(yaml_output).unwrap();
assert!(yaml_string.contains("# EnvelopeCLI Full Database Export"));
assert!(yaml_string.contains("Checking"));
assert!(yaml_string.contains("Groceries"));
}
#[test]
fn test_yaml_roundtrip() {
let (_temp_dir, storage) = create_test_storage();
let account = Account::new("Checking", AccountType::Checking);
storage.accounts.upsert(account).unwrap();
storage.accounts.save().unwrap();
let mut yaml_output = Vec::new();
export_full_yaml(&storage, &mut yaml_output).unwrap();
let yaml_string = String::from_utf8(yaml_output).unwrap();
let yaml_content: String = yaml_string
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<_>>()
.join("\n");
let imported = import_from_yaml(&yaml_content).unwrap();
assert_eq!(imported.accounts.len(), 1);
assert_eq!(imported.accounts[0].name, "Checking");
}
}