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