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}