Skip to main content

modkit_db/secure/
tx_error.rs

1//! Transaction error types for typed domain transactions.
2//!
3//! These types allow domain errors to be propagated through `SeaORM` transactions
4//! without mutex-based storage or string parsing.
5
6use std::fmt;
7
8/// Infrastructure error representing a database-level failure.
9///
10/// This wraps database errors (connection issues, constraint violations, etc.)
11/// in a type that does not expose `SeaORM` internals.
12#[derive(Debug, Clone)]
13pub struct InfraError {
14    message: String,
15}
16
17impl InfraError {
18    /// Create a new infrastructure error from a message.
19    pub fn new(message: impl Into<String>) -> Self {
20        Self {
21            message: message.into(),
22        }
23    }
24
25    /// Get the error message.
26    #[must_use]
27    pub fn message(&self) -> &str {
28        &self.message
29    }
30}
31
32impl fmt::Display for InfraError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(f, "{}", self.message)
35    }
36}
37
38impl std::error::Error for InfraError {}
39
40/// Transaction error that distinguishes domain errors from infrastructure errors.
41///
42/// This type is returned by [`SecureConn::in_transaction`] and allows callers to
43/// handle domain errors separately from database infrastructure failures.
44///
45/// # Example
46///
47/// ```ignore
48/// use modkit_db::secure::{SecureConn, TxError};
49///
50/// let result = db.in_transaction(|tx| Box::pin(async move {
51///     // Domain logic that may return DomainError
52///     repo.create(tx, user).await
53/// })).await;
54///
55/// let user = result.map_err(|e| e.into_domain(DomainError::database_infra))?;
56/// ```
57#[derive(Debug, Clone)]
58pub enum TxError<E> {
59    /// A domain error returned from the transaction callback.
60    Domain(E),
61    /// An infrastructure error from the database layer.
62    Infra(InfraError),
63}
64
65impl<E> TxError<E> {
66    /// Convert this transaction error into a domain error.
67    ///
68    /// If this is already a domain error, returns it directly.
69    /// If this is an infrastructure error, uses the provided mapping function
70    /// to convert it into a domain error.
71    pub fn into_domain<F>(self, map_infra: F) -> E
72    where
73        F: FnOnce(InfraError) -> E,
74    {
75        match self {
76            TxError::Domain(e) => e,
77            TxError::Infra(infra) => map_infra(infra),
78        }
79    }
80}
81
82impl<E: fmt::Display> fmt::Display for TxError<E> {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            TxError::Domain(e) => write!(f, "{e}"),
86            TxError::Infra(e) => write!(f, "infrastructure error: {e}"),
87        }
88    }
89}
90
91impl<E: fmt::Debug + fmt::Display> std::error::Error for TxError<E> {}