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    pub fn store_not_found(key: impl Into<String>) -> Self {
54        let key = key.into();
55
56        Self {
57            class: ErrorClass::NotFound,
58            origin: ErrorOrigin::Store,
59            message: format!("data key not found: {key}"),
60            detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
61        }
62    }
63
64    /// Construct a standardized unsupported-entity-path error.
65    pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
66        let path = path.into();
67
68        Self::new(
69            ErrorClass::Unsupported,
70            ErrorOrigin::Store,
71            format!("unsupported entity path: '{path}'"),
72        )
73    }
74
75    #[must_use]
76    pub const fn is_not_found(&self) -> bool {
77        matches!(
78            self.detail,
79            Some(ErrorDetail::Store(StoreError::NotFound { .. }))
80        )
81    }
82
83    #[must_use]
84    pub fn display_with_class(&self) -> String {
85        format!("{}:{}: {}", self.origin, self.class, self.message)
86    }
87}
88
89///
90/// ErrorDetail
91///
92/// Structured, origin-specific error detail carried by [`InternalError`].
93/// This enum is intentionally extensible.
94///
95
96#[derive(Debug, ThisError)]
97pub enum ErrorDetail {
98    #[error("{0}")]
99    Store(StoreError),
100    #[error("{0}")]
101    ViewPatch(crate::patch::MergePatchError),
102    // Future-proofing:
103    // #[error("{0}")]
104    // Index(IndexError),
105    //
106    // #[error("{0}")]
107    // Query(QueryErrorDetail),
108    //
109    // #[error("{0}")]
110    // Executor(ExecutorErrorDetail),
111}
112
113impl From<MergePatchError> for InternalError {
114    fn from(err: MergePatchError) -> Self {
115        Self {
116            class: ErrorClass::Unsupported,
117            origin: ErrorOrigin::Interface,
118            message: err.to_string(),
119            detail: Some(ErrorDetail::ViewPatch(err)),
120        }
121    }
122}
123
124///
125/// StoreError
126///
127/// Store-specific structured error detail.
128/// Never returned directly; always wrapped in [`ErrorDetail::Store`].
129///
130
131#[derive(Debug, ThisError)]
132pub enum StoreError {
133    #[error("key not found: {key}")]
134    NotFound { key: String },
135
136    #[error("store corruption: {message}")]
137    Corrupt { message: String },
138
139    #[error("store invariant violation: {message}")]
140    InvariantViolation { message: String },
141}
142
143///
144/// ErrorClass
145/// Internal error taxonomy for runtime classification.
146/// Not a stable API; may change without notice.
147///
148
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub enum ErrorClass {
151    Corruption,
152    NotFound,
153    Internal,
154    Conflict,
155    Unsupported,
156    InvariantViolation,
157}
158
159impl fmt::Display for ErrorClass {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        let label = match self {
162            Self::Corruption => "corruption",
163            Self::NotFound => "not_found",
164            Self::Internal => "internal",
165            Self::Conflict => "conflict",
166            Self::Unsupported => "unsupported",
167            Self::InvariantViolation => "invariant_violation",
168        };
169        write!(f, "{label}")
170    }
171}
172
173///
174/// ErrorOrigin
175/// Internal origin taxonomy for runtime classification.
176/// Not a stable API; may change without notice.
177///
178
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub enum ErrorOrigin {
181    Serialize,
182    Store,
183    Index,
184    Query,
185    Response,
186    Executor,
187    Interface,
188}
189
190impl fmt::Display for ErrorOrigin {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        let label = match self {
193            Self::Serialize => "serialize",
194            Self::Store => "store",
195            Self::Index => "index",
196            Self::Query => "query",
197            Self::Response => "response",
198            Self::Executor => "executor",
199            Self::Interface => "interface",
200        };
201        write!(f, "{label}")
202    }
203}