1#[cfg(test)]
2mod tests;
3
4#[cfg(test)]
5use crate::db::query::plan::{PlanError, PlanPolicyError, PlanUserError, PolicyPlanError};
6use crate::{
7 db::{access::AccessPlanError, cursor::CursorPlanError},
8 patch::MergePatchError,
9};
10use std::fmt;
11use thiserror::Error as ThisError;
12
13#[derive(Debug, ThisError)]
114#[error("{message}")]
115pub struct InternalError {
116 pub class: ErrorClass,
117 pub origin: ErrorOrigin,
118 pub message: String,
119
120 pub detail: Option<ErrorDetail>,
123}
124
125impl InternalError {
126 pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
130 let message = message.into();
131
132 let detail = match (class, origin) {
133 (ErrorClass::Corruption, ErrorOrigin::Store) => {
134 Some(ErrorDetail::Store(StoreError::Corrupt {
135 message: message.clone(),
136 }))
137 }
138 (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
139 Some(ErrorDetail::Store(StoreError::InvariantViolation {
140 message: message.clone(),
141 }))
142 }
143 _ => None,
144 };
145
146 Self {
147 class,
148 origin,
149 message,
150 detail,
151 }
152 }
153
154 pub(crate) fn classified(
156 class: ErrorClass,
157 origin: ErrorOrigin,
158 message: impl Into<String>,
159 ) -> Self {
160 Self::new(class, origin, message)
161 }
162
163 pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
165 Self::classified(self.class, self.origin, message)
166 }
167
168 pub(crate) fn with_origin(self, origin: ErrorOrigin) -> Self {
172 Self::classified(self.class, origin, self.message)
173 }
174
175 pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
177 Self::new(
178 ErrorClass::InvariantViolation,
179 ErrorOrigin::Query,
180 message.into(),
181 )
182 }
183
184 pub(crate) fn planner_invariant(message: impl Into<String>) -> Self {
186 Self::new(
187 ErrorClass::InvariantViolation,
188 ErrorOrigin::Planner,
189 message.into(),
190 )
191 }
192
193 #[must_use]
195 pub(crate) fn executor_invariant_message(reason: impl Into<String>) -> String {
196 format!("executor invariant violated: {}", reason.into())
197 }
198
199 #[must_use]
201 pub(crate) fn invalid_logical_plan_message(reason: impl Into<String>) -> String {
202 format!("invalid logical plan: {}", reason.into())
203 }
204
205 pub(crate) fn query_executor_invariant(reason: impl Into<String>) -> Self {
207 Self::query_invariant(Self::executor_invariant_message(reason))
208 }
209
210 pub(crate) fn query_invalid_logical_plan(reason: impl Into<String>) -> Self {
212 Self::planner_invariant(Self::invalid_logical_plan_message(reason))
213 }
214
215 pub(crate) fn cursor_invariant(message: impl Into<String>) -> Self {
217 Self::new(
218 ErrorClass::InvariantViolation,
219 ErrorOrigin::Cursor,
220 message.into(),
221 )
222 }
223
224 pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
226 Self::new(
227 ErrorClass::InvariantViolation,
228 ErrorOrigin::Index,
229 message.into(),
230 )
231 }
232
233 pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
235 Self::new(
236 ErrorClass::InvariantViolation,
237 ErrorOrigin::Executor,
238 message.into(),
239 )
240 }
241
242 pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
244 Self::new(
245 ErrorClass::InvariantViolation,
246 ErrorOrigin::Store,
247 message.into(),
248 )
249 }
250
251 pub(crate) fn store_internal(message: impl Into<String>) -> Self {
253 Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
254 }
255
256 pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
258 Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
259 }
260
261 pub(crate) fn index_internal(message: impl Into<String>) -> Self {
263 Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
264 }
265
266 #[cfg(test)]
268 pub(crate) fn query_internal(message: impl Into<String>) -> Self {
269 Self::new(ErrorClass::Internal, ErrorOrigin::Query, message.into())
270 }
271
272 pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
274 Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
275 }
276
277 pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
279 Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
280 }
281
282 pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
284 Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
285 }
286
287 pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
289 Self::new(
290 ErrorClass::Corruption,
291 ErrorOrigin::Serialize,
292 message.into(),
293 )
294 }
295
296 pub(crate) fn identity_corruption(message: impl Into<String>) -> Self {
298 Self::new(
299 ErrorClass::Corruption,
300 ErrorOrigin::Identity,
301 message.into(),
302 )
303 }
304
305 pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
307 Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
308 }
309
310 pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
312 Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
313 }
314
315 pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
317 Self::new(
318 ErrorClass::Unsupported,
319 ErrorOrigin::Executor,
320 message.into(),
321 )
322 }
323
324 pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
326 Self::new(
327 ErrorClass::Unsupported,
328 ErrorOrigin::Serialize,
329 message.into(),
330 )
331 }
332
333 pub(crate) fn cursor_unsupported(message: impl Into<String>) -> Self {
335 Self::new(ErrorClass::Unsupported, ErrorOrigin::Cursor, message.into())
336 }
337
338 pub fn store_not_found(key: impl Into<String>) -> Self {
339 let key = key.into();
340
341 Self {
342 class: ErrorClass::NotFound,
343 origin: ErrorOrigin::Store,
344 message: format!("data key not found: {key}"),
345 detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
346 }
347 }
348
349 pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
351 let path = path.into();
352
353 Self::new(
354 ErrorClass::Unsupported,
355 ErrorOrigin::Store,
356 format!("unsupported entity path: '{path}'"),
357 )
358 }
359
360 #[must_use]
361 pub const fn is_not_found(&self) -> bool {
362 matches!(
363 self.detail,
364 Some(ErrorDetail::Store(StoreError::NotFound { .. }))
365 )
366 }
367
368 #[must_use]
369 pub fn display_with_class(&self) -> String {
370 format!("{}:{}: {}", self.origin, self.class, self.message)
371 }
372
373 pub(crate) fn index_plan_corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
375 let message = message.into();
376 Self::new(
377 ErrorClass::Corruption,
378 origin,
379 format!("corruption detected ({origin}): {message}"),
380 )
381 }
382
383 pub(crate) fn index_plan_index_corruption(message: impl Into<String>) -> Self {
385 Self::index_plan_corruption(ErrorOrigin::Index, message)
386 }
387
388 pub(crate) fn index_plan_store_corruption(message: impl Into<String>) -> Self {
390 Self::index_plan_corruption(ErrorOrigin::Store, message)
391 }
392
393 pub(crate) fn index_plan_serialize_corruption(message: impl Into<String>) -> Self {
395 Self::index_plan_corruption(ErrorOrigin::Serialize, message)
396 }
397
398 pub(crate) fn index_plan_invariant(origin: ErrorOrigin, message: impl Into<String>) -> Self {
400 let message = message.into();
401 Self::new(
402 ErrorClass::InvariantViolation,
403 origin,
404 format!("invariant violation detected ({origin}): {message}"),
405 )
406 }
407
408 pub(crate) fn index_plan_store_invariant(message: impl Into<String>) -> Self {
410 Self::index_plan_invariant(ErrorOrigin::Store, message)
411 }
412
413 pub(crate) fn index_violation(path: &str, index_fields: &[&str]) -> Self {
415 Self::new(
416 ErrorClass::Conflict,
417 ErrorOrigin::Index,
418 format!(
419 "index constraint violation: {path} ({})",
420 index_fields.join(", ")
421 ),
422 )
423 }
424
425 pub(crate) fn from_cursor_plan_error(err: CursorPlanError) -> Self {
431 match err {
432 CursorPlanError::ContinuationCursorInvariantViolation { reason } => {
433 Self::cursor_invariant(reason)
434 }
435 CursorPlanError::InvalidContinuationCursor { .. }
436 | CursorPlanError::InvalidContinuationCursorPayload { .. }
437 | CursorPlanError::ContinuationCursorVersionMismatch { .. }
438 | CursorPlanError::ContinuationCursorSignatureMismatch { .. }
439 | CursorPlanError::ContinuationCursorBoundaryArityMismatch { .. }
440 | CursorPlanError::ContinuationCursorWindowMismatch { .. }
441 | CursorPlanError::ContinuationCursorBoundaryTypeMismatch { .. }
442 | CursorPlanError::ContinuationCursorPrimaryKeyTypeMismatch { .. } => {
443 Self::cursor_unsupported(err.to_string())
444 }
445 }
446 }
447
448 #[cfg(test)]
450 pub(crate) fn from_group_plan_error(err: PlanError) -> Self {
451 let message = match err {
452 PlanError::User(inner) => match *inner {
453 PlanUserError::Group(inner) => {
454 Self::invalid_logical_plan_message(inner.to_string())
455 }
456 other => {
457 format!("group-plan error conversion received non-group user variant: {other}")
458 }
459 },
460 PlanError::Policy(inner) => match *inner {
461 PlanPolicyError::Group(inner) => {
462 Self::invalid_logical_plan_message(inner.to_string())
463 }
464 PlanPolicyError::Policy(inner) => format!(
465 "group-plan error conversion received non-group policy variant: {inner}"
466 ),
467 },
468 PlanError::Cursor(inner) => {
469 format!("group-plan error conversion received cursor variant: {inner}")
470 }
471 };
472
473 Self::planner_invariant(message)
474 }
475
476 pub(crate) fn from_executor_access_plan_error(err: AccessPlanError) -> Self {
478 Self::query_invariant(err.to_string())
479 }
480
481 #[cfg(test)]
484 pub(crate) fn plan_invariant_violation(err: PolicyPlanError) -> Self {
485 let reason = match err {
486 PolicyPlanError::EmptyOrderSpec => {
487 "order specification must include at least one field"
488 }
489 PolicyPlanError::DeletePlanWithOffset => "delete plans must not include OFFSET",
490 PolicyPlanError::DeletePlanWithGrouping => {
491 "delete plans must not include GROUP BY or HAVING"
492 }
493 PolicyPlanError::DeletePlanWithPagination => "delete plans must not include pagination",
494 PolicyPlanError::LoadPlanWithDeleteLimit => "load plans must not carry delete limits",
495 PolicyPlanError::DeleteLimitRequiresOrder => "delete limit requires explicit ordering",
496 PolicyPlanError::UnorderedPagination => "pagination requires explicit ordering",
497 };
498
499 Self::planner_invariant(Self::executor_invariant_message(reason))
500 }
501}
502
503#[derive(Debug, ThisError)]
511pub enum ErrorDetail {
512 #[error("{0}")]
513 Store(StoreError),
514 #[error("{0}")]
515 ViewPatch(crate::patch::MergePatchError),
516 }
526
527impl From<MergePatchError> for InternalError {
528 fn from(err: MergePatchError) -> Self {
529 Self {
530 class: ErrorClass::Unsupported,
531 origin: ErrorOrigin::Interface,
532 message: err.to_string(),
533 detail: Some(ErrorDetail::ViewPatch(err)),
534 }
535 }
536}
537
538#[derive(Debug, ThisError)]
546pub enum StoreError {
547 #[error("key not found: {key}")]
548 NotFound { key: String },
549
550 #[error("store corruption: {message}")]
551 Corrupt { message: String },
552
553 #[error("store invariant violation: {message}")]
554 InvariantViolation { message: String },
555}
556
557#[derive(Clone, Copy, Debug, Eq, PartialEq)]
564pub enum ErrorClass {
565 Corruption,
566 NotFound,
567 Internal,
568 Conflict,
569 Unsupported,
570 InvariantViolation,
571}
572
573impl fmt::Display for ErrorClass {
574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575 let label = match self {
576 Self::Corruption => "corruption",
577 Self::NotFound => "not_found",
578 Self::Internal => "internal",
579 Self::Conflict => "conflict",
580 Self::Unsupported => "unsupported",
581 Self::InvariantViolation => "invariant_violation",
582 };
583 write!(f, "{label}")
584 }
585}
586
587#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub enum ErrorOrigin {
595 Serialize,
596 Store,
597 Index,
598 Identity,
599 Query,
600 Planner,
601 Cursor,
602 Recovery,
603 Response,
604 Executor,
605 Interface,
606}
607
608impl fmt::Display for ErrorOrigin {
609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610 let label = match self {
611 Self::Serialize => "serialize",
612 Self::Store => "store",
613 Self::Index => "index",
614 Self::Identity => "identity",
615 Self::Query => "query",
616 Self::Planner => "planner",
617 Self::Cursor => "cursor",
618 Self::Recovery => "recovery",
619 Self::Response => "response",
620 Self::Executor => "executor",
621 Self::Interface => "interface",
622 };
623 write!(f, "{label}")
624 }
625}