1#[cfg(test)]
8mod tests;
9
10#[cfg(test)]
11use crate::db::query::plan::{PlanError, PlanPolicyError, PlanUserError, PolicyPlanError};
12use crate::{
13 db::{access::AccessPlanError, cursor::CursorPlanError},
14 patch::MergePatchError,
15};
16use std::fmt;
17use thiserror::Error as ThisError;
18
19#[derive(Debug, ThisError)]
120#[error("{message}")]
121pub struct InternalError {
122 pub(crate) class: ErrorClass,
123 pub(crate) origin: ErrorOrigin,
124 pub(crate) message: String,
125
126 pub(crate) detail: Option<ErrorDetail>,
129}
130
131impl InternalError {
132 pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
136 let message = message.into();
137
138 let detail = match (class, origin) {
139 (ErrorClass::Corruption, ErrorOrigin::Store) => {
140 Some(ErrorDetail::Store(StoreError::Corrupt {
141 message: message.clone(),
142 }))
143 }
144 (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
145 Some(ErrorDetail::Store(StoreError::InvariantViolation {
146 message: message.clone(),
147 }))
148 }
149 _ => None,
150 };
151
152 Self {
153 class,
154 origin,
155 message,
156 detail,
157 }
158 }
159
160 #[must_use]
162 pub const fn class(&self) -> ErrorClass {
163 self.class
164 }
165
166 #[must_use]
168 pub const fn origin(&self) -> ErrorOrigin {
169 self.origin
170 }
171
172 #[must_use]
174 pub fn message(&self) -> &str {
175 &self.message
176 }
177
178 #[must_use]
180 pub const fn detail(&self) -> Option<&ErrorDetail> {
181 self.detail.as_ref()
182 }
183
184 #[must_use]
186 pub fn into_message(self) -> String {
187 self.message
188 }
189
190 pub(crate) fn classified(
192 class: ErrorClass,
193 origin: ErrorOrigin,
194 message: impl Into<String>,
195 ) -> Self {
196 Self::new(class, origin, message)
197 }
198
199 pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
201 Self::classified(self.class, self.origin, message)
202 }
203
204 pub(crate) fn with_origin(self, origin: ErrorOrigin) -> Self {
208 Self::classified(self.class, origin, self.message)
209 }
210
211 pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
213 Self::new(
214 ErrorClass::InvariantViolation,
215 ErrorOrigin::Query,
216 message.into(),
217 )
218 }
219
220 pub(crate) fn planner_invariant(message: impl Into<String>) -> Self {
222 Self::new(
223 ErrorClass::InvariantViolation,
224 ErrorOrigin::Planner,
225 message.into(),
226 )
227 }
228
229 #[must_use]
231 pub(crate) fn executor_invariant_message(reason: impl Into<String>) -> String {
232 format!("executor invariant violated: {}", reason.into())
233 }
234
235 #[must_use]
237 pub(crate) fn invalid_logical_plan_message(reason: impl Into<String>) -> String {
238 format!("invalid logical plan: {}", reason.into())
239 }
240
241 pub(crate) fn query_executor_invariant(reason: impl Into<String>) -> Self {
243 Self::query_invariant(Self::executor_invariant_message(reason))
244 }
245
246 pub(crate) fn query_invalid_logical_plan(reason: impl Into<String>) -> Self {
248 Self::planner_invariant(Self::invalid_logical_plan_message(reason))
249 }
250
251 pub(crate) fn cursor_invariant(message: impl Into<String>) -> Self {
253 Self::new(
254 ErrorClass::InvariantViolation,
255 ErrorOrigin::Cursor,
256 message.into(),
257 )
258 }
259
260 pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
262 Self::new(
263 ErrorClass::InvariantViolation,
264 ErrorOrigin::Index,
265 message.into(),
266 )
267 }
268
269 pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
271 Self::new(
272 ErrorClass::InvariantViolation,
273 ErrorOrigin::Executor,
274 message.into(),
275 )
276 }
277
278 pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
280 Self::new(
281 ErrorClass::InvariantViolation,
282 ErrorOrigin::Store,
283 message.into(),
284 )
285 }
286
287 pub(crate) fn store_internal(message: impl Into<String>) -> Self {
289 Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
290 }
291
292 pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
294 Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
295 }
296
297 pub(crate) fn index_internal(message: impl Into<String>) -> Self {
299 Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
300 }
301
302 #[cfg(test)]
304 pub(crate) fn query_internal(message: impl Into<String>) -> Self {
305 Self::new(ErrorClass::Internal, ErrorOrigin::Query, message.into())
306 }
307
308 pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
310 Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
311 }
312
313 pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
315 Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
316 }
317
318 pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
320 Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
321 }
322
323 pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
325 Self::new(
326 ErrorClass::Corruption,
327 ErrorOrigin::Serialize,
328 message.into(),
329 )
330 }
331
332 pub(crate) fn identity_corruption(message: impl Into<String>) -> Self {
334 Self::new(
335 ErrorClass::Corruption,
336 ErrorOrigin::Identity,
337 message.into(),
338 )
339 }
340
341 pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
343 Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
344 }
345
346 pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
348 Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
349 }
350
351 pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
353 Self::new(
354 ErrorClass::Unsupported,
355 ErrorOrigin::Executor,
356 message.into(),
357 )
358 }
359
360 pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
362 Self::new(
363 ErrorClass::Unsupported,
364 ErrorOrigin::Serialize,
365 message.into(),
366 )
367 }
368
369 pub(crate) fn cursor_unsupported(message: impl Into<String>) -> Self {
371 Self::new(ErrorClass::Unsupported, ErrorOrigin::Cursor, message.into())
372 }
373
374 pub fn store_not_found(key: impl Into<String>) -> Self {
375 let key = key.into();
376
377 Self {
378 class: ErrorClass::NotFound,
379 origin: ErrorOrigin::Store,
380 message: format!("data key not found: {key}"),
381 detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
382 }
383 }
384
385 pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
387 let path = path.into();
388
389 Self::new(
390 ErrorClass::Unsupported,
391 ErrorOrigin::Store,
392 format!("unsupported entity path: '{path}'"),
393 )
394 }
395
396 #[must_use]
397 pub const fn is_not_found(&self) -> bool {
398 matches!(
399 self.detail,
400 Some(ErrorDetail::Store(StoreError::NotFound { .. }))
401 )
402 }
403
404 #[must_use]
405 pub fn display_with_class(&self) -> String {
406 format!("{}:{}: {}", self.origin, self.class, self.message)
407 }
408
409 pub(crate) fn index_plan_corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
411 let message = message.into();
412 Self::new(
413 ErrorClass::Corruption,
414 origin,
415 format!("corruption detected ({origin}): {message}"),
416 )
417 }
418
419 pub(crate) fn index_plan_index_corruption(message: impl Into<String>) -> Self {
421 Self::index_plan_corruption(ErrorOrigin::Index, message)
422 }
423
424 pub(crate) fn index_plan_store_corruption(message: impl Into<String>) -> Self {
426 Self::index_plan_corruption(ErrorOrigin::Store, message)
427 }
428
429 pub(crate) fn index_plan_serialize_corruption(message: impl Into<String>) -> Self {
431 Self::index_plan_corruption(ErrorOrigin::Serialize, message)
432 }
433
434 pub(crate) fn index_plan_invariant(origin: ErrorOrigin, message: impl Into<String>) -> Self {
436 let message = message.into();
437 Self::new(
438 ErrorClass::InvariantViolation,
439 origin,
440 format!("invariant violation detected ({origin}): {message}"),
441 )
442 }
443
444 pub(crate) fn index_plan_store_invariant(message: impl Into<String>) -> Self {
446 Self::index_plan_invariant(ErrorOrigin::Store, message)
447 }
448
449 pub(crate) fn index_violation(path: &str, index_fields: &[&str]) -> Self {
451 Self::new(
452 ErrorClass::Conflict,
453 ErrorOrigin::Index,
454 format!(
455 "index constraint violation: {path} ({})",
456 index_fields.join(", ")
457 ),
458 )
459 }
460
461 pub(crate) fn from_cursor_plan_error(err: CursorPlanError) -> Self {
467 match err {
468 CursorPlanError::ContinuationCursorInvariantViolation { reason } => {
469 Self::cursor_invariant(reason)
470 }
471 CursorPlanError::InvalidContinuationCursor { .. }
472 | CursorPlanError::InvalidContinuationCursorPayload { .. }
473 | CursorPlanError::ContinuationCursorVersionMismatch { .. }
474 | CursorPlanError::ContinuationCursorSignatureMismatch { .. }
475 | CursorPlanError::ContinuationCursorBoundaryArityMismatch { .. }
476 | CursorPlanError::ContinuationCursorWindowMismatch { .. }
477 | CursorPlanError::ContinuationCursorBoundaryTypeMismatch { .. }
478 | CursorPlanError::ContinuationCursorPrimaryKeyTypeMismatch { .. } => {
479 Self::cursor_unsupported(err.to_string())
480 }
481 }
482 }
483
484 #[cfg(test)]
486 pub(crate) fn from_group_plan_error(err: PlanError) -> Self {
487 let message = match err {
488 PlanError::User(inner) => match *inner {
489 PlanUserError::Group(inner) => {
490 Self::invalid_logical_plan_message(inner.to_string())
491 }
492 other => {
493 format!("group-plan error conversion received non-group user variant: {other}")
494 }
495 },
496 PlanError::Policy(inner) => match *inner {
497 PlanPolicyError::Group(inner) => {
498 Self::invalid_logical_plan_message(inner.to_string())
499 }
500 PlanPolicyError::Policy(inner) => format!(
501 "group-plan error conversion received non-group policy variant: {inner}"
502 ),
503 },
504 PlanError::Cursor(inner) => {
505 format!("group-plan error conversion received cursor variant: {inner}")
506 }
507 };
508
509 Self::planner_invariant(message)
510 }
511
512 pub(crate) fn from_executor_access_plan_error(err: AccessPlanError) -> Self {
514 Self::query_invariant(err.to_string())
515 }
516
517 #[cfg(test)]
520 pub(crate) fn plan_invariant_violation(err: PolicyPlanError) -> Self {
521 let reason = match err {
522 PolicyPlanError::EmptyOrderSpec => {
523 "order specification must include at least one field"
524 }
525 PolicyPlanError::DeletePlanWithOffset => "delete plans must not include OFFSET",
526 PolicyPlanError::DeletePlanWithGrouping => {
527 "delete plans must not include GROUP BY or HAVING"
528 }
529 PolicyPlanError::DeletePlanWithPagination => "delete plans must not include pagination",
530 PolicyPlanError::LoadPlanWithDeleteLimit => "load plans must not carry delete limits",
531 PolicyPlanError::DeleteLimitRequiresOrder => "delete limit requires explicit ordering",
532 PolicyPlanError::UnorderedPagination => "pagination requires explicit ordering",
533 };
534
535 Self::planner_invariant(Self::executor_invariant_message(reason))
536 }
537}
538
539#[derive(Debug, ThisError)]
547pub enum ErrorDetail {
548 #[error("{0}")]
549 Store(StoreError),
550 #[error("{0}")]
551 ViewPatch(crate::patch::MergePatchError),
552 }
562
563impl From<MergePatchError> for InternalError {
564 fn from(err: MergePatchError) -> Self {
565 Self {
566 class: ErrorClass::Unsupported,
567 origin: ErrorOrigin::Interface,
568 message: err.to_string(),
569 detail: Some(ErrorDetail::ViewPatch(err)),
570 }
571 }
572}
573
574#[derive(Debug, ThisError)]
582pub enum StoreError {
583 #[error("key not found: {key}")]
584 NotFound { key: String },
585
586 #[error("store corruption: {message}")]
587 Corrupt { message: String },
588
589 #[error("store invariant violation: {message}")]
590 InvariantViolation { message: String },
591}
592
593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
600pub enum ErrorClass {
601 Corruption,
602 NotFound,
603 Internal,
604 Conflict,
605 Unsupported,
606 InvariantViolation,
607}
608
609impl fmt::Display for ErrorClass {
610 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611 let label = match self {
612 Self::Corruption => "corruption",
613 Self::NotFound => "not_found",
614 Self::Internal => "internal",
615 Self::Conflict => "conflict",
616 Self::Unsupported => "unsupported",
617 Self::InvariantViolation => "invariant_violation",
618 };
619 write!(f, "{label}")
620 }
621}
622
623#[derive(Clone, Copy, Debug, Eq, PartialEq)]
630pub enum ErrorOrigin {
631 Serialize,
632 Store,
633 Index,
634 Identity,
635 Query,
636 Planner,
637 Cursor,
638 Recovery,
639 Response,
640 Executor,
641 Interface,
642}
643
644impl fmt::Display for ErrorOrigin {
645 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646 let label = match self {
647 Self::Serialize => "serialize",
648 Self::Store => "store",
649 Self::Index => "index",
650 Self::Identity => "identity",
651 Self::Query => "query",
652 Self::Planner => "planner",
653 Self::Cursor => "cursor",
654 Self::Recovery => "recovery",
655 Self::Response => "response",
656 Self::Executor => "executor",
657 Self::Interface => "interface",
658 };
659 write!(f, "{label}")
660 }
661}