Skip to main content

kernex_memory/
error.rs

1//! Per-crate error type for `kernex-memory`.
2//!
3//! Replaces the old `kernex_core::KernexError::Store(String)` shape so callers
4//! can pattern-match on the actual cause (e.g. distinguish a connection
5//! failure from a missing-row condition). Foreign errors (`sqlx::Error`,
6//! `std::io::Error`) are preserved as `#[source]` so the chain is intact.
7
8/// Errors produced by the memory store.
9#[derive(Debug, thiserror::Error)]
10pub enum MemoryError {
11    /// A SQLite operation failed. `context` describes what we were trying to
12    /// do; `source` is the underlying `sqlx::Error` for chain inspection.
13    #[error("{context}: {source}")]
14    Sqlite {
15        /// Human-readable description of the failing operation
16        /// (e.g. "advance task", "fts search", "record token usage").
17        context: String,
18        /// The underlying sqlx error.
19        #[source]
20        source: sqlx::Error,
21    },
22
23    /// A filesystem operation failed. `context` describes what we were trying
24    /// to do; `source` is the underlying `std::io::Error`.
25    #[error("{context}: {source}")]
26    Io {
27        /// Human-readable description of the failing operation
28        /// (e.g. "create data dir", "open audit log").
29        context: String,
30        /// The underlying I/O error.
31        #[source]
32        source: std::io::Error,
33    },
34
35    /// JSON serialization or deserialization failed.
36    #[error("{context}: {source}")]
37    Serde {
38        /// Human-readable description of the failing operation.
39        context: String,
40        /// The underlying serde_json error.
41        #[source]
42        source: serde_json::Error,
43    },
44
45    /// Domain-logic error with no foreign source — invalid arguments,
46    /// missing rows, malformed input, etc.
47    #[error("{0}")]
48    Logic(String),
49}
50
51impl MemoryError {
52    /// Wrap a `sqlx::Error` with operation context.
53    pub fn sqlite(context: impl Into<String>, source: sqlx::Error) -> Self {
54        Self::Sqlite {
55            context: context.into(),
56            source,
57        }
58    }
59
60    /// Wrap a `std::io::Error` with operation context.
61    pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
62        Self::Io {
63            context: context.into(),
64            source,
65        }
66    }
67
68    /// Wrap a `serde_json::Error` with operation context.
69    pub fn serde(context: impl Into<String>, source: serde_json::Error) -> Self {
70        Self::Serde {
71            context: context.into(),
72            source,
73        }
74    }
75
76    /// Construct a domain-logic error from a message.
77    pub fn logic(msg: impl Into<String>) -> Self {
78        Self::Logic(msg.into())
79    }
80}
81
82/// Bridge to the workspace-level aggregate error.
83///
84/// Boxes the typed `MemoryError` inside `KernexError::Store` so callers
85/// downstream can recover the structured cause via
86/// `boxed.downcast_ref::<MemoryError>()` and pattern-match on the
87/// concrete variant (e.g. `MemoryError::Sqlite { source, .. }` to inspect
88/// the underlying `sqlx::Error`).
89impl From<MemoryError> for kernex_core::error::KernexError {
90    fn from(err: MemoryError) -> Self {
91        kernex_core::error::KernexError::store(err)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn sqlite_display_includes_context_and_source() {
101        let inner = sqlx::Error::PoolTimedOut;
102        let err = MemoryError::sqlite("acquire conn", inner);
103        let msg = format!("{err}");
104        assert!(msg.contains("acquire conn"), "msg was {msg:?}");
105        assert!(msg.contains("pool"), "msg was {msg:?}");
106    }
107
108    #[test]
109    fn logic_display_passthrough() {
110        let err = MemoryError::logic("session not found");
111        assert_eq!(format!("{err}"), "session not found");
112    }
113}