Skip to main content

icydb_core/
error.rs

1use crate::patch::MergePatchError;
2use std::fmt;
3use thiserror::Error as ThisError;
4
5///
6/// InternalError
7///
8/// Structured runtime error with a stable internal classification.
9/// Not a stable API; intended for internal use and may change without notice.
10///
11
12#[derive(Debug, ThisError)]
13#[error("{message}")]
14pub struct InternalError {
15    pub class: ErrorClass,
16    pub origin: ErrorOrigin,
17    pub message: String,
18
19    /// Optional structured error detail.
20    /// The variant (if present) must correspond to `origin`.
21    pub detail: Option<ErrorDetail>,
22}
23
24impl InternalError {
25    /// Construct an InternalError with optional origin-specific detail.
26    /// This constructor provides default StoreError details for certain
27    /// (class, origin) combinations but does not guarantee a detail payload.
28    pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
29        let message = message.into();
30
31        let detail = match (class, origin) {
32            (ErrorClass::Corruption, ErrorOrigin::Store) => {
33                Some(ErrorDetail::Store(StoreError::Corrupt {
34                    message: message.clone(),
35                }))
36            }
37            (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
38                Some(ErrorDetail::Store(StoreError::InvariantViolation {
39                    message: message.clone(),
40                }))
41            }
42            _ => None,
43        };
44
45        Self {
46            class,
47            origin,
48            message,
49            detail,
50        }
51    }
52
53    /// Construct a query-origin invariant violation.
54    pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
55        Self::new(
56            ErrorClass::InvariantViolation,
57            ErrorOrigin::Query,
58            message.into(),
59        )
60    }
61
62    /// Construct an index-origin invariant violation.
63    pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
64        Self::new(
65            ErrorClass::InvariantViolation,
66            ErrorOrigin::Index,
67            message.into(),
68        )
69    }
70
71    /// Construct an executor-origin invariant violation.
72    pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
73        Self::new(
74            ErrorClass::InvariantViolation,
75            ErrorOrigin::Executor,
76            message.into(),
77        )
78    }
79
80    /// Construct a store-origin invariant violation.
81    pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
82        Self::new(
83            ErrorClass::InvariantViolation,
84            ErrorOrigin::Store,
85            message.into(),
86        )
87    }
88
89    /// Construct a corruption error for a specific origin.
90    pub(crate) fn corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
91        Self::new(ErrorClass::Corruption, origin, message.into())
92    }
93
94    /// Construct a store-origin internal error.
95    pub(crate) fn store_internal(message: impl Into<String>) -> Self {
96        Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
97    }
98
99    /// Construct an executor-origin internal error.
100    pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
101        Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
102    }
103
104    /// Construct an index-origin internal error.
105    pub(crate) fn index_internal(message: impl Into<String>) -> Self {
106        Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
107    }
108
109    /// Construct a store-origin corruption error.
110    pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
111        Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
112    }
113
114    /// Construct an index-origin corruption error.
115    pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
116        Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
117    }
118
119    /// Construct a serialize-origin corruption error.
120    pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
121        Self::new(
122            ErrorClass::Corruption,
123            ErrorOrigin::Serialize,
124            message.into(),
125        )
126    }
127
128    /// Construct a store-origin unsupported error.
129    pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
130        Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
131    }
132
133    /// Construct an index-origin unsupported error.
134    pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
135        Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
136    }
137
138    /// Construct an executor-origin unsupported error.
139    pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
140        Self::new(
141            ErrorClass::Unsupported,
142            ErrorOrigin::Executor,
143            message.into(),
144        )
145    }
146
147    /// Construct a serialize-origin unsupported error.
148    pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
149        Self::new(
150            ErrorClass::Unsupported,
151            ErrorOrigin::Serialize,
152            message.into(),
153        )
154    }
155
156    pub fn store_not_found(key: impl Into<String>) -> Self {
157        let key = key.into();
158
159        Self {
160            class: ErrorClass::NotFound,
161            origin: ErrorOrigin::Store,
162            message: format!("data key not found: {key}"),
163            detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
164        }
165    }
166
167    /// Construct a standardized unsupported-entity-path error.
168    pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
169        let path = path.into();
170
171        Self::new(
172            ErrorClass::Unsupported,
173            ErrorOrigin::Store,
174            format!("unsupported entity path: '{path}'"),
175        )
176    }
177
178    #[must_use]
179    pub const fn is_not_found(&self) -> bool {
180        matches!(
181            self.detail,
182            Some(ErrorDetail::Store(StoreError::NotFound { .. }))
183        )
184    }
185
186    #[must_use]
187    pub fn display_with_class(&self) -> String {
188        format!("{}:{}: {}", self.origin, self.class, self.message)
189    }
190}
191
192///
193/// ErrorDetail
194///
195/// Structured, origin-specific error detail carried by [`InternalError`].
196/// This enum is intentionally extensible.
197///
198
199#[derive(Debug, ThisError)]
200pub enum ErrorDetail {
201    #[error("{0}")]
202    Store(StoreError),
203    #[error("{0}")]
204    ViewPatch(crate::patch::MergePatchError),
205    // Future-proofing:
206    // #[error("{0}")]
207    // Index(IndexError),
208    //
209    // #[error("{0}")]
210    // Query(QueryErrorDetail),
211    //
212    // #[error("{0}")]
213    // Executor(ExecutorErrorDetail),
214}
215
216impl From<MergePatchError> for InternalError {
217    fn from(err: MergePatchError) -> Self {
218        Self {
219            class: ErrorClass::Unsupported,
220            origin: ErrorOrigin::Interface,
221            message: err.to_string(),
222            detail: Some(ErrorDetail::ViewPatch(err)),
223        }
224    }
225}
226
227///
228/// StoreError
229///
230/// Store-specific structured error detail.
231/// Never returned directly; always wrapped in [`ErrorDetail::Store`].
232///
233
234#[derive(Debug, ThisError)]
235pub enum StoreError {
236    #[error("key not found: {key}")]
237    NotFound { key: String },
238
239    #[error("store corruption: {message}")]
240    Corrupt { message: String },
241
242    #[error("store invariant violation: {message}")]
243    InvariantViolation { message: String },
244}
245
246///
247/// ErrorClass
248/// Internal error taxonomy for runtime classification.
249/// Not a stable API; may change without notice.
250///
251
252#[derive(Clone, Copy, Debug, Eq, PartialEq)]
253pub enum ErrorClass {
254    Corruption,
255    NotFound,
256    Internal,
257    Conflict,
258    Unsupported,
259    InvariantViolation,
260}
261
262impl fmt::Display for ErrorClass {
263    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264        let label = match self {
265            Self::Corruption => "corruption",
266            Self::NotFound => "not_found",
267            Self::Internal => "internal",
268            Self::Conflict => "conflict",
269            Self::Unsupported => "unsupported",
270            Self::InvariantViolation => "invariant_violation",
271        };
272        write!(f, "{label}")
273    }
274}
275
276///
277/// ErrorOrigin
278/// Internal origin taxonomy for runtime classification.
279/// Not a stable API; may change without notice.
280///
281
282#[derive(Clone, Copy, Debug, Eq, PartialEq)]
283pub enum ErrorOrigin {
284    Serialize,
285    Store,
286    Index,
287    Query,
288    Response,
289    Executor,
290    Interface,
291}
292
293impl fmt::Display for ErrorOrigin {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        let label = match self {
296            Self::Serialize => "serialize",
297            Self::Store => "store",
298            Self::Index => "index",
299            Self::Query => "query",
300            Self::Response => "response",
301            Self::Executor => "executor",
302            Self::Interface => "interface",
303        };
304        write!(f, "{label}")
305    }
306}