Files
zesdex/crates/zesdex-cms/src/application/conversation_service.rs
T
asepharyana ab1a54b72e Refactor error handling in IAM and CMS crates
- Introduced `RepositoryError` and `ServiceError` enums in both IAM and CMS domains for better error management.
- Updated domain traits and services to return specific error types instead of `anyhow::Result`.
- Enhanced session and OAuth repository implementations to handle errors more explicitly.
- Refactored session service methods to return `Result<T, ServiceError>` for improved error handling.
- Updated HTTP handlers to utilize the new error types.
- Modified password hashing functions to run in a blocking context using `tokio::task::spawn_blocking`.
- Added tests for new error handling mechanisms and async password functions.
2026-07-20 06:14:23 +07:00

105 lines
4.1 KiB
Rust

//! Conversation use-case implementations for the CMS.
//!
//! `ConversationServiceImpl` implements `ConversationService` (defined in
//! `domain::service`) and is generic over `R: ConversationRepository`
//! (defined in `domain::repository`), delegating all persistence to that
//! adapter. The repository is injected at composition root.
//!
//! ## Flow
//! Each method computes the session directory from the session ID, then
//! delegates the actual I/O to the injected `repo`. Error context is
//! added at this layer to identify which session caused the failure.
use std::path::PathBuf;
use tracing;
use crate::domain::conversation::{ChatMessage, Conversation};
use crate::domain::error::ServiceError;
use crate::domain::repository::ConversationRepository;
use crate::domain::service::ConversationService;
/// Service implementation for conversation CRUD operations.
///
/// Generic over `R: ConversationRepository` so the persistence layer
/// can be swapped without changing business logic.
///
/// ## Fields
/// - `repo` — injected conversation repository implementation
/// - `sessions_dir` — base path under which session directories live
pub struct ConversationServiceImpl<R> {
pub repo: R,
/// Base directory containing session subdirectories.
pub sessions_dir: PathBuf,
}
impl<R: ConversationRepository> ConversationServiceImpl<R> {
/// Create a new service with the given repository and sessions directory.
///
/// ## Parameters
/// - `repo` — the repository adapter to delegate persistence to
/// - `sessions_dir` — base path for session directories (converted via `Into`)
pub fn new(repo: R, sessions_dir: impl Into<PathBuf>) -> Self {
tracing::debug!("creating ConversationServiceImpl");
Self {
repo,
sessions_dir: sessions_dir.into(),
}
}
/// Compute the session directory for a given session id.
///
/// Returns `{sessions_dir}/{session_id}`.
fn session_dir(&self, session_id: &str) -> PathBuf {
self.sessions_dir.join(session_id)
}
}
impl<R: ConversationRepository> ConversationService for ConversationServiceImpl<R> {
/// Load a conversation from disk for the given session.
///
/// Flow: resolve session dir → delegate to repo.load() → wrap error with context.
fn load_conversation(&self, session_id: &str) -> Result<Conversation, ServiceError> {
tracing::debug!("loading conversation for session {session_id}");
let dir = self.session_dir(session_id);
self.repo.load(&dir).map_err(|e| {
ServiceError::Other(format!(
"failed to load conversation for session '{session_id}': {e}"
))
})
}
/// Persist a conversation to disk.
///
/// Flow: resolve session dir from conv.session_id → delegate to repo.save() → wrap error.
fn save_conversation(&self, conv: &Conversation) -> Result<(), ServiceError> {
tracing::debug!("saving conversation for session {}", conv.session_id);
let dir = self.session_dir(&conv.session_id);
self.repo.save(&dir, conv).map_err(|e| {
ServiceError::Other(format!(
"failed to save conversation for session '{}': {e}",
conv.session_id
))
})
}
/// Add a message to a conversation and persist immediately.
///
/// Flow: push message to in-memory conversation → resolve session dir → delegate save.
///
/// ## Note
/// This is a write-through operation: the message is appended to the
/// in-memory `Conversation` and then the full conversation is persisted.
fn add_message(&self, conv: &mut Conversation, msg: ChatMessage) -> Result<(), ServiceError> {
tracing::debug!("adding message to session {}", conv.session_id);
conv.push(msg); // append message to in-memory conversation
let dir = self.session_dir(&conv.session_id);
self.repo.save(&dir, conv).map_err(|e| {
ServiceError::Other(format!(
"failed to persist conversation after adding message for session '{}': {e}",
conv.session_id
))
})
}
}