//! JSON file–backed `ConversationRepository` implementation. //! //! Stores `Conversation` as pretty-printed JSON at `/conversation.json`. //! Uses atomic write (temp file + rename + fsync) for crash safety. //! //! ## Data Flow //! - `load()`: read file → deserialize JSON → return Conversation //! - `save()`: serialize Conversation → atomic write to conversation.json use std::path::Path; use anyhow::{Context, Result}; use zesdex_utils::write_json_atomic; use crate::domain::conversation::Conversation; use crate::domain::repository::ConversationRepository; /// File-based `ConversationRepository` that reads/writes `conversation.json`. /// /// Zero-allocation: the struct is a unit type marker. #[derive(Debug, Clone, Default)] pub struct JsonConversationRepository; impl JsonConversationRepository { /// Create a new repository instance. pub fn new() -> Self { Self } } impl ConversationRepository for JsonConversationRepository { /// Load a `Conversation` from `/conversation.json`. /// /// Flow: read file → parse JSON → return Conversation. fn load(&self, session_dir: &Path) -> Result { let path = session_dir.join("conversation.json"); let data = std::fs::read_to_string(&path) .with_context(|| format!("failed to read conversation at '{}'", path.display()))?; let conv: Conversation = serde_json::from_str(&data) .with_context(|| format!("failed to parse conversation at '{}'", path.display()))?; Ok(conv) } /// Persist a `Conversation` to `/conversation.json`. /// /// Flow: create session dir → atomic JSON write → log success. fn save(&self, session_dir: &Path, conversation: &Conversation) -> Result<()> { tracing::debug!("saving conversation to {session_dir:?}"); std::fs::create_dir_all(session_dir) .with_context(|| format!("failed to create session dir '{}'", session_dir.display()))?; let path = session_dir.join("conversation.json"); write_json_atomic(&path, conversation, None) .with_context(|| "failed to save conversation")?; tracing::debug!("conversation saved to '{}'", path.display()); Ok(()) } }