zakat_sqlite/
persistence.rs

1//! Persistence traits for Ledger Events.
2//!
3//! Defines the `LedgerStore` trait for saving and loading ledger events.
4
5use async_trait::async_trait;
6use std::path::PathBuf;
7use zakat_core::types::{ZakatError, InvalidInputDetails};
8use zakat_ledger::events::LedgerEvent;
9
10/// Trait for persisting Zakat Ledger events.
11#[async_trait]
12pub trait LedgerStore: Send + Sync {
13    /// Saves a single event to the store.
14    async fn save_event(&self, event: &LedgerEvent) -> Result<(), ZakatError>;
15    
16    /// Loads all events from the store.
17    async fn load_events(&self) -> Result<Vec<LedgerEvent>, ZakatError>;
18}
19
20/// A simple JSON file-based implementation of LedgerStore.
21pub struct JsonFileStore {
22    path: PathBuf,
23}
24
25impl JsonFileStore {
26    pub fn new(path: impl Into<PathBuf>) -> Self {
27        Self { path: path.into() }
28    }
29}
30
31#[async_trait]
32impl LedgerStore for JsonFileStore {
33    async fn save_event(&self, event: &LedgerEvent) -> Result<(), ZakatError> {
34        let mut events = self.load_events().await.unwrap_or_default();
35        events.push(event.clone());
36        
37        let json = serde_json::to_string_pretty(&events).map_err(|e| ZakatError::InvalidInput(Box::new(InvalidInputDetails { 
38            code: zakat_core::types::ZakatErrorCode::InvalidInput,
39            field: "ledger".to_string(), 
40            value: "json".to_string(),
41            reason_key: "error-parse-json".to_string(),
42            args: Some(std::collections::HashMap::from([("error".to_string(), e.to_string())])),
43            source_label: Some("JsonFileStore".to_string()),
44            ..Default::default()
45        })))?;
46        
47        tokio::fs::write(&self.path, json).await.map_err(|e: std::io::Error| ZakatError::NetworkError(format!("IO Error: {}", e)))?;
48        Ok(())
49    }
50
51    async fn load_events(&self) -> Result<Vec<LedgerEvent>, ZakatError> {
52        if !self.path.exists() {
53            return Ok(Vec::new());
54        }
55        
56        let content = tokio::fs::read_to_string(&self.path).await.map_err(|e: std::io::Error| ZakatError::NetworkError(format!("IO Error: {}", e)))?;
57        
58        if content.trim().is_empty() {
59            return Ok(Vec::new());
60        }
61        
62        serde_json::from_str(&content).map_err(|e| ZakatError::InvalidInput(Box::new(InvalidInputDetails {
63            code: zakat_core::types::ZakatErrorCode::InvalidInput,
64            field: "ledger".to_string(),
65            value: "json".to_string(),
66            reason_key: "error-parse-json".to_string(),
67            args: Some(std::collections::HashMap::from([("error".to_string(), e.to_string())])),
68            source_label: Some("JsonFileStore".to_string()),
69            ..Default::default()
70        })))
71    }
72}