Skip to main content

icydb_core/
error.rs

1use crate::{
2    db::query::plan::{CursorPlanError, PlanError},
3    patch::MergePatchError,
4};
5use std::fmt;
6use thiserror::Error as ThisError;
7
8///
9/// InternalError
10///
11/// Structured runtime error with a stable internal classification.
12/// Not a stable API; intended for internal use and may change without notice.
13///
14
15#[derive(Debug, ThisError)]
16#[error("{message}")]
17pub struct InternalError {
18    pub class: ErrorClass,
19    pub origin: ErrorOrigin,
20    pub message: String,
21
22    /// Optional structured error detail.
23    /// The variant (if present) must correspond to `origin`.
24    pub detail: Option<ErrorDetail>,
25}
26
27impl InternalError {
28    /// Construct an InternalError with optional origin-specific detail.
29    /// This constructor provides default StoreError details for certain
30    /// (class, origin) combinations but does not guarantee a detail payload.
31    pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
32        let message = message.into();
33
34        let detail = match (class, origin) {
35            (ErrorClass::Corruption, ErrorOrigin::Store) => {
36                Some(ErrorDetail::Store(StoreError::Corrupt {
37                    message: message.clone(),
38                }))
39            }
40            (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
41                Some(ErrorDetail::Store(StoreError::InvariantViolation {
42                    message: message.clone(),
43                }))
44            }
45            _ => None,
46        };
47
48        Self {
49            class,
50            origin,
51            message,
52            detail,
53        }
54    }
55
56    /// Construct an error while preserving an explicit class/origin taxonomy pair.
57    pub(crate) fn classified(
58        class: ErrorClass,
59        origin: ErrorOrigin,
60        message: impl Into<String>,
61    ) -> Self {
62        Self::new(class, origin, message)
63    }
64
65    /// Rebuild this error with a new message while preserving class/origin taxonomy.
66    pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
67        Self::classified(self.class, self.origin, message)
68    }
69
70    /// Construct a query-origin invariant violation.
71    pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
72        Self::new(
73            ErrorClass::InvariantViolation,
74            ErrorOrigin::Query,
75            message.into(),
76        )
77    }
78
79    /// Construct an index-origin invariant violation.
80    pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
81        Self::new(
82            ErrorClass::InvariantViolation,
83            ErrorOrigin::Index,
84            message.into(),
85        )
86    }
87
88    /// Construct an executor-origin invariant violation.
89    pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
90        Self::new(
91            ErrorClass::InvariantViolation,
92            ErrorOrigin::Executor,
93            message.into(),
94        )
95    }
96
97    /// Construct a store-origin invariant violation.
98    pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
99        Self::new(
100            ErrorClass::InvariantViolation,
101            ErrorOrigin::Store,
102            message.into(),
103        )
104    }
105
106    /// Construct a store-origin internal error.
107    pub(crate) fn store_internal(message: impl Into<String>) -> Self {
108        Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
109    }
110
111    /// Construct an executor-origin internal error.
112    pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
113        Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
114    }
115
116    /// Construct an index-origin internal error.
117    pub(crate) fn index_internal(message: impl Into<String>) -> Self {
118        Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
119    }
120
121    /// Construct a query-origin internal error.
122    #[cfg(test)]
123    pub(crate) fn query_internal(message: impl Into<String>) -> Self {
124        Self::new(ErrorClass::Internal, ErrorOrigin::Query, message.into())
125    }
126
127    /// Construct a serialize-origin internal error.
128    pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
129        Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
130    }
131
132    /// Construct a store-origin corruption error.
133    pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
134        Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
135    }
136
137    /// Construct an index-origin corruption error.
138    pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
139        Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
140    }
141
142    /// Construct a serialize-origin corruption error.
143    pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
144        Self::new(
145            ErrorClass::Corruption,
146            ErrorOrigin::Serialize,
147            message.into(),
148        )
149    }
150
151    /// Construct a store-origin unsupported error.
152    pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
153        Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
154    }
155
156    /// Construct an index-origin unsupported error.
157    pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
158        Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
159    }
160
161    /// Construct an executor-origin unsupported error.
162    pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
163        Self::new(
164            ErrorClass::Unsupported,
165            ErrorOrigin::Executor,
166            message.into(),
167        )
168    }
169
170    /// Construct a serialize-origin unsupported error.
171    pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
172        Self::new(
173            ErrorClass::Unsupported,
174            ErrorOrigin::Serialize,
175            message.into(),
176        )
177    }
178
179    pub fn store_not_found(key: impl Into<String>) -> Self {
180        let key = key.into();
181
182        Self {
183            class: ErrorClass::NotFound,
184            origin: ErrorOrigin::Store,
185            message: format!("data key not found: {key}"),
186            detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
187        }
188    }
189
190    /// Construct a standardized unsupported-entity-path error.
191    pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
192        let path = path.into();
193
194        Self::new(
195            ErrorClass::Unsupported,
196            ErrorOrigin::Store,
197            format!("unsupported entity path: '{path}'"),
198        )
199    }
200
201    #[must_use]
202    pub const fn is_not_found(&self) -> bool {
203        matches!(
204            self.detail,
205            Some(ErrorDetail::Store(StoreError::NotFound { .. }))
206        )
207    }
208
209    #[must_use]
210    pub fn display_with_class(&self) -> String {
211        format!("{}:{}: {}", self.origin, self.class, self.message)
212    }
213
214    /// Construct an index-plan corruption error with a canonical prefix.
215    pub(crate) fn index_plan_corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
216        let message = message.into();
217        Self::new(
218            ErrorClass::Corruption,
219            origin,
220            format!("corruption detected ({origin}): {message}"),
221        )
222    }
223
224    /// Construct an index uniqueness violation conflict error.
225    pub(crate) fn index_violation(path: &str, index_fields: &[&str]) -> Self {
226        Self::new(
227            ErrorClass::Conflict,
228            ErrorOrigin::Index,
229            format!(
230                "index constraint violation: {path} ({})",
231                index_fields.join(", ")
232            ),
233        )
234    }
235
236    /// Map plan-surface cursor failures into executor-boundary invariants.
237    pub(crate) fn from_cursor_plan_error(err: PlanError) -> Self {
238        let message = match &err {
239            PlanError::Cursor(inner) => match inner.as_ref() {
240                CursorPlanError::ContinuationCursorBoundaryArityMismatch { expected: 1, found } => {
241                    format!(
242                        "executor invariant violated: pk-ordered continuation boundary must contain exactly 1 slot, found {found}"
243                    )
244                }
245                CursorPlanError::ContinuationCursorPrimaryKeyTypeMismatch {
246                    value: None, ..
247                } => "executor invariant violated: pk cursor slot must be present".to_string(),
248                CursorPlanError::ContinuationCursorPrimaryKeyTypeMismatch {
249                    value: Some(_),
250                    ..
251                } => "executor invariant violated: pk cursor slot type mismatch".to_string(),
252                _ => err.to_string(),
253            },
254            _ => err.to_string(),
255        };
256
257        Self::query_invariant(message)
258    }
259
260    /// Map shared plan-validation failures into executor-boundary invariants.
261    pub(crate) fn from_executor_plan_error(err: PlanError) -> Self {
262        Self::query_invariant(err.to_string())
263    }
264}
265
266///
267/// ErrorDetail
268///
269/// Structured, origin-specific error detail carried by [`InternalError`].
270/// This enum is intentionally extensible.
271///
272
273#[derive(Debug, ThisError)]
274pub enum ErrorDetail {
275    #[error("{0}")]
276    Store(StoreError),
277    #[error("{0}")]
278    ViewPatch(crate::patch::MergePatchError),
279    // Future-proofing:
280    // #[error("{0}")]
281    // Index(IndexError),
282    //
283    // #[error("{0}")]
284    // Query(QueryErrorDetail),
285    //
286    // #[error("{0}")]
287    // Executor(ExecutorErrorDetail),
288}
289
290impl From<MergePatchError> for InternalError {
291    fn from(err: MergePatchError) -> Self {
292        Self {
293            class: ErrorClass::Unsupported,
294            origin: ErrorOrigin::Interface,
295            message: err.to_string(),
296            detail: Some(ErrorDetail::ViewPatch(err)),
297        }
298    }
299}
300
301///
302/// StoreError
303///
304/// Store-specific structured error detail.
305/// Never returned directly; always wrapped in [`ErrorDetail::Store`].
306///
307
308#[derive(Debug, ThisError)]
309pub enum StoreError {
310    #[error("key not found: {key}")]
311    NotFound { key: String },
312
313    #[error("store corruption: {message}")]
314    Corrupt { message: String },
315
316    #[error("store invariant violation: {message}")]
317    InvariantViolation { message: String },
318}
319
320///
321/// ErrorClass
322/// Internal error taxonomy for runtime classification.
323/// Not a stable API; may change without notice.
324///
325
326#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327pub enum ErrorClass {
328    Corruption,
329    NotFound,
330    Internal,
331    Conflict,
332    Unsupported,
333    InvariantViolation,
334}
335
336impl fmt::Display for ErrorClass {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        let label = match self {
339            Self::Corruption => "corruption",
340            Self::NotFound => "not_found",
341            Self::Internal => "internal",
342            Self::Conflict => "conflict",
343            Self::Unsupported => "unsupported",
344            Self::InvariantViolation => "invariant_violation",
345        };
346        write!(f, "{label}")
347    }
348}
349
350///
351/// ErrorOrigin
352/// Internal origin taxonomy for runtime classification.
353/// Not a stable API; may change without notice.
354///
355
356#[derive(Clone, Copy, Debug, Eq, PartialEq)]
357pub enum ErrorOrigin {
358    Serialize,
359    Store,
360    Index,
361    Query,
362    Response,
363    Executor,
364    Interface,
365}
366
367impl fmt::Display for ErrorOrigin {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        let label = match self {
370            Self::Serialize => "serialize",
371            Self::Store => "store",
372            Self::Index => "index",
373            Self::Query => "query",
374            Self::Response => "response",
375            Self::Executor => "executor",
376            Self::Interface => "interface",
377        };
378        write!(f, "{label}")
379    }
380}