Skip to main content

hydracache_db/
error.rs

1use std::fmt;
2
3use thiserror::Error;
4
5/// Database adapter kind attached to operation diagnostics.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum DbAdapterKind {
8    /// Database-neutral repository or custom loader path.
9    Generic,
10    /// SQLx-facing adapter helper.
11    Sqlx,
12    /// Diesel-facing adapter helper.
13    Diesel,
14    /// SeaORM-facing adapter helper.
15    SeaOrm,
16}
17
18impl fmt::Display for DbAdapterKind {
19    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20        formatter.write_str(match self {
21            Self::Generic => "generic",
22            Self::Sqlx => "sqlx",
23            Self::Diesel => "diesel",
24            Self::SeaOrm => "seaorm",
25        })
26    }
27}
28
29/// Cached database result shape attached to operation diagnostics.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum DbResultShape {
32    /// Exactly one row or value.
33    One,
34    /// Optional row or value.
35    Optional,
36    /// Collection result.
37    All,
38    /// Repository/custom result shape selected by the caller.
39    Custom,
40}
41
42impl fmt::Display for DbResultShape {
43    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44        formatter.write_str(match self {
45            Self::One => "one",
46            Self::Optional => "optional",
47            Self::All => "all",
48            Self::Custom => "custom",
49        })
50    }
51}
52
53/// Diagnostic context for one database cache operation.
54///
55/// The context is deliberately database-neutral. It describes the cache-side
56/// operation and adapter helper while the database library keeps ownership of
57/// typed database errors, transactions, query construction, and row mapping.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct DbOperationContext {
60    /// Adapter helper that initiated the operation.
61    pub adapter: DbAdapterKind,
62    /// Diagnostic operation name.
63    pub operation: String,
64    /// Cache namespace used to build physical keys.
65    pub namespace: String,
66    /// Physical cache key when one was available.
67    pub physical_key: Option<String>,
68    /// Result shape requested by the caller or adapter helper.
69    pub result_shape: DbResultShape,
70}
71
72impl fmt::Display for DbOperationContext {
73    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
74        let namespace = if self.namespace.is_empty() {
75            "<empty>"
76        } else {
77            &self.namespace
78        };
79        let physical_key = self.physical_key.as_deref().unwrap_or("<missing>");
80
81        write!(
82            formatter,
83            "adapter={}, namespace={}, key={}, result_shape={}",
84            self.adapter, namespace, physical_key, self.result_shape
85        )
86    }
87}
88
89/// Error type returned by database cache adapter helpers.
90#[derive(Debug, Error)]
91pub enum DbCacheError {
92    /// A cached database operation cannot run without an explicit cache key.
93    #[error(
94        "database cached operation `{operation}` is missing an explicit cache key \
95         (adapter={adapter}, namespace={namespace}, result_shape={result_shape})"
96    )]
97    MissingKey {
98        /// Diagnostic operation name.
99        operation: String,
100        /// Adapter helper that initiated the operation.
101        adapter: DbAdapterKind,
102        /// Cache namespace used for physical cache keys.
103        namespace: String,
104        /// Result shape requested by the caller or adapter helper.
105        result_shape: DbResultShape,
106    },
107
108    /// The underlying cache operation failed with database-cache context.
109    #[error("database cached operation `{operation}` failed ({context}): {source}")]
110    Operation {
111        /// Diagnostic operation name.
112        operation: String,
113        /// Database cache operation context.
114        context: Box<DbOperationContext>,
115        /// Underlying cache-layer error.
116        #[source]
117        source: Box<hydracache::CacheError>,
118    },
119
120    /// The underlying HydraCache operation failed.
121    #[error(transparent)]
122    Cache(#[from] hydracache::CacheError),
123}
124
125impl DbCacheError {
126    pub(crate) fn operation(context: DbOperationContext, source: hydracache::CacheError) -> Self {
127        Self::Operation {
128            operation: context.operation.clone(),
129            context: Box::new(context),
130            source: Box::new(source),
131        }
132    }
133}
134
135/// Database cache adapter result type.
136pub type Result<T> = std::result::Result<T, DbCacheError>;