1use crate::{
2 db::query::plan::{CursorPlanError, PlanError},
3 patch::MergePatchError,
4};
5use std::fmt;
6use thiserror::Error as ThisError;
7
8#[derive(Debug, ThisError)]
16#[error("{message}")]
17pub struct InternalError {
18 pub class: ErrorClass,
19 pub origin: ErrorOrigin,
20 pub message: String,
21
22 pub detail: Option<ErrorDetail>,
25}
26
27impl InternalError {
28 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 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 pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
67 Self::classified(self.class, self.origin, message)
68 }
69
70 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 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 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 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 pub(crate) fn store_internal(message: impl Into<String>) -> Self {
108 Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
109 }
110
111 pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
113 Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
114 }
115
116 pub(crate) fn index_internal(message: impl Into<String>) -> Self {
118 Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
119 }
120
121 #[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 pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
129 Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
130 }
131
132 pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
134 Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
135 }
136
137 pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
139 Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
140 }
141
142 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 pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
153 Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
154 }
155
156 pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
158 Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
159 }
160
161 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 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 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 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 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 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 pub(crate) fn from_executor_plan_error(err: PlanError) -> Self {
262 Self::query_invariant(err.to_string())
263 }
264}
265
266#[derive(Debug, ThisError)]
274pub enum ErrorDetail {
275 #[error("{0}")]
276 Store(StoreError),
277 #[error("{0}")]
278 ViewPatch(crate::patch::MergePatchError),
279 }
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#[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#[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#[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}