Skip to main content

icydb_core/
error.rs

1use std::fmt;
2use thiserror::Error as ThisError;
3
4///
5/// InternalError
6///
7/// Structured runtime error with a stable internal classification.
8/// Not a stable API; intended for internal use and may change without notice.
9///
10#[derive(Debug, ThisError)]
11#[error("{message}")]
12pub struct InternalError {
13    pub class: ErrorClass,
14    pub origin: ErrorOrigin,
15    pub message: String,
16
17    /// Optional structured error detail.
18    /// The variant (if present) must correspond to `origin`.
19    pub detail: Option<ErrorDetail>,
20}
21
22impl InternalError {
23    /// Construct an InternalError with optional origin-specific detail.
24    /// This constructor provides default StoreError details for certain
25    /// (class, origin) combinations but does not guarantee a detail payload.
26    pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
27        let message = message.into();
28
29        let detail = match (class, origin) {
30            (ErrorClass::Corruption, ErrorOrigin::Store) => {
31                Some(ErrorDetail::Store(StoreError::Corrupt {
32                    message: message.clone(),
33                }))
34            }
35            (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
36                Some(ErrorDetail::Store(StoreError::InvariantViolation {
37                    message: message.clone(),
38                }))
39            }
40            _ => None,
41        };
42
43        Self {
44            class,
45            origin,
46            message,
47            detail,
48        }
49    }
50
51    pub fn store_not_found(key: impl Into<String>) -> Self {
52        let key = key.into();
53
54        Self {
55            class: ErrorClass::NotFound,
56            origin: ErrorOrigin::Store,
57            message: format!("data key not found: {key}"),
58            detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
59        }
60    }
61
62    #[must_use]
63    pub const fn is_not_found(&self) -> bool {
64        matches!(
65            self.detail,
66            Some(ErrorDetail::Store(StoreError::NotFound { .. }))
67        )
68    }
69
70    #[must_use]
71    pub fn display_with_class(&self) -> String {
72        format!("{}:{}: {}", self.origin, self.class, self.message)
73    }
74}
75
76impl From<crate::traits::ViewPatchError> for InternalError {
77    fn from(err: crate::traits::ViewPatchError) -> Self {
78        let class = match err.leaf() {
79            crate::traits::ViewPatchError::MissingKey { .. } => ErrorClass::NotFound,
80            _ => ErrorClass::Unsupported,
81        };
82
83        Self {
84            class,
85            origin: ErrorOrigin::Interface,
86            message: err.to_string(),
87            detail: Some(ErrorDetail::ViewPatch(err)),
88        }
89    }
90}
91
92///
93/// ErrorDetail
94///
95/// Structured, origin-specific error detail carried by [`InternalError`].
96/// This enum is intentionally extensible.
97///
98
99#[derive(Debug, ThisError)]
100pub enum ErrorDetail {
101    #[error("{0}")]
102    Store(StoreError),
103    #[error("{0}")]
104    ViewPatch(crate::traits::ViewPatchError),
105    // Future-proofing:
106    // #[error("{0}")]
107    // Index(IndexError),
108    //
109    // #[error("{0}")]
110    // Query(QueryErrorDetail),
111    //
112    // #[error("{0}")]
113    // Executor(ExecutorErrorDetail),
114}
115
116///
117/// StoreError
118///
119/// Store-specific structured error detail.
120/// Never returned directly; always wrapped in [`ErrorDetail::Store`].
121///
122
123#[derive(Debug, ThisError)]
124pub enum StoreError {
125    #[error("key not found: {key}")]
126    NotFound { key: String },
127
128    #[error("store corruption: {message}")]
129    Corrupt { message: String },
130
131    #[error("store invariant violation: {message}")]
132    InvariantViolation { message: String },
133}
134
135///
136/// ErrorClass
137/// Internal error taxonomy for runtime classification.
138/// Not a stable API; may change without notice.
139///
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142pub enum ErrorClass {
143    Corruption,
144    NotFound,
145    Internal,
146    Conflict,
147    Unsupported,
148    InvariantViolation,
149}
150
151impl fmt::Display for ErrorClass {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        let label = match self {
154            Self::Corruption => "corruption",
155            Self::NotFound => "not_found",
156            Self::Internal => "internal",
157            Self::Conflict => "conflict",
158            Self::Unsupported => "unsupported",
159            Self::InvariantViolation => "invariant_violation",
160        };
161        write!(f, "{label}")
162    }
163}
164
165///
166/// ErrorOrigin
167/// Internal origin taxonomy for runtime classification.
168/// Not a stable API; may change without notice.
169///
170
171#[derive(Clone, Copy, Debug, Eq, PartialEq)]
172pub enum ErrorOrigin {
173    Serialize,
174    Store,
175    Index,
176    Query,
177    Response,
178    Executor,
179    Interface,
180}
181
182impl fmt::Display for ErrorOrigin {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        let label = match self {
185            Self::Serialize => "serialize",
186            Self::Store => "store",
187            Self::Index => "index",
188            Self::Query => "query",
189            Self::Response => "response",
190            Self::Executor => "executor",
191            Self::Interface => "interface",
192        };
193        write!(f, "{label}")
194    }
195}