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(crate) class: ErrorClass,
117 pub(crate) origin: ErrorOrigin,
118 pub(crate) message: String,
119
120 pub(crate) 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 #[must_use]
156 pub const fn class(&self) -> ErrorClass {
157 self.class
158 }
159
160 #[must_use]
162 pub const fn origin(&self) -> ErrorOrigin {
163 self.origin
164 }
165
166 #[must_use]
168 pub fn message(&self) -> &str {
169 &self.message
170 }
171
172 #[must_use]
174 pub const fn detail(&self) -> Option<&ErrorDetail> {
175 self.detail.as_ref()
176 }
177
178 #[must_use]
180 pub fn into_message(self) -> String {
181 self.message
182 }
183
184 pub(crate) fn classified(
186 class: ErrorClass,
187 origin: ErrorOrigin,
188 message: impl Into<String>,
189 ) -> Self {
190 Self::new(class, origin, message)
191 }
192
193 pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
195 Self::classified(self.class, self.origin, message)
196 }
197
198 pub(crate) fn with_origin(self, origin: ErrorOrigin) -> Self {
202 Self::classified(self.class, origin, self.message)
203 }
204
205 pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
207 Self::new(
208 ErrorClass::InvariantViolation,
209 ErrorOrigin::Query,
210 message.into(),
211 )
212 }
213
214 pub(crate) fn planner_invariant(message: impl Into<String>) -> Self {
216 Self::new(
217 ErrorClass::InvariantViolation,
218 ErrorOrigin::Planner,
219 message.into(),
220 )
221 }
222
223 #[must_use]
225 pub(crate) fn executor_invariant_message(reason: impl Into<String>) -> String {
226 format!("executor invariant violated: {}", reason.into())
227 }
228
229 #[must_use]
231 pub(crate) fn invalid_logical_plan_message(reason: impl Into<String>) -> String {
232 format!("invalid logical plan: {}", reason.into())
233 }
234
235 pub(crate) fn query_executor_invariant(reason: impl Into<String>) -> Self {
237 Self::query_invariant(Self::executor_invariant_message(reason))
238 }
239
240 pub(crate) fn query_invalid_logical_plan(reason: impl Into<String>) -> Self {
242 Self::planner_invariant(Self::invalid_logical_plan_message(reason))
243 }
244
245 pub(crate) fn cursor_invariant(message: impl Into<String>) -> Self {
247 Self::new(
248 ErrorClass::InvariantViolation,
249 ErrorOrigin::Cursor,
250 message.into(),
251 )
252 }
253
254 pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
256 Self::new(
257 ErrorClass::InvariantViolation,
258 ErrorOrigin::Index,
259 message.into(),
260 )
261 }
262
263 pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
265 Self::new(
266 ErrorClass::InvariantViolation,
267 ErrorOrigin::Executor,
268 message.into(),
269 )
270 }
271
272 pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
274 Self::new(
275 ErrorClass::InvariantViolation,
276 ErrorOrigin::Store,
277 message.into(),
278 )
279 }
280
281 pub(crate) fn store_internal(message: impl Into<String>) -> Self {
283 Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
284 }
285
286 pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
288 Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
289 }
290
291 pub(crate) fn index_internal(message: impl Into<String>) -> Self {
293 Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
294 }
295
296 #[cfg(test)]
298 pub(crate) fn query_internal(message: impl Into<String>) -> Self {
299 Self::new(ErrorClass::Internal, ErrorOrigin::Query, message.into())
300 }
301
302 pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
304 Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
305 }
306
307 pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
309 Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
310 }
311
312 pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
314 Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
315 }
316
317 pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
319 Self::new(
320 ErrorClass::Corruption,
321 ErrorOrigin::Serialize,
322 message.into(),
323 )
324 }
325
326 pub(crate) fn identity_corruption(message: impl Into<String>) -> Self {
328 Self::new(
329 ErrorClass::Corruption,
330 ErrorOrigin::Identity,
331 message.into(),
332 )
333 }
334
335 pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
337 Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
338 }
339
340 pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
342 Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
343 }
344
345 pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
347 Self::new(
348 ErrorClass::Unsupported,
349 ErrorOrigin::Executor,
350 message.into(),
351 )
352 }
353
354 pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
356 Self::new(
357 ErrorClass::Unsupported,
358 ErrorOrigin::Serialize,
359 message.into(),
360 )
361 }
362
363 pub(crate) fn cursor_unsupported(message: impl Into<String>) -> Self {
365 Self::new(ErrorClass::Unsupported, ErrorOrigin::Cursor, message.into())
366 }
367
368 pub fn store_not_found(key: impl Into<String>) -> Self {
369 let key = key.into();
370
371 Self {
372 class: ErrorClass::NotFound,
373 origin: ErrorOrigin::Store,
374 message: format!("data key not found: {key}"),
375 detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
376 }
377 }
378
379 pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
381 let path = path.into();
382
383 Self::new(
384 ErrorClass::Unsupported,
385 ErrorOrigin::Store,
386 format!("unsupported entity path: '{path}'"),
387 )
388 }
389
390 #[must_use]
391 pub const fn is_not_found(&self) -> bool {
392 matches!(
393 self.detail,
394 Some(ErrorDetail::Store(StoreError::NotFound { .. }))
395 )
396 }
397
398 #[must_use]
399 pub fn display_with_class(&self) -> String {
400 format!("{}:{}: {}", self.origin, self.class, self.message)
401 }
402
403 pub(crate) fn index_plan_corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
405 let message = message.into();
406 Self::new(
407 ErrorClass::Corruption,
408 origin,
409 format!("corruption detected ({origin}): {message}"),
410 )
411 }
412
413 pub(crate) fn index_plan_index_corruption(message: impl Into<String>) -> Self {
415 Self::index_plan_corruption(ErrorOrigin::Index, message)
416 }
417
418 pub(crate) fn index_plan_store_corruption(message: impl Into<String>) -> Self {
420 Self::index_plan_corruption(ErrorOrigin::Store, message)
421 }
422
423 pub(crate) fn index_plan_serialize_corruption(message: impl Into<String>) -> Self {
425 Self::index_plan_corruption(ErrorOrigin::Serialize, message)
426 }
427
428 pub(crate) fn index_plan_invariant(origin: ErrorOrigin, message: impl Into<String>) -> Self {
430 let message = message.into();
431 Self::new(
432 ErrorClass::InvariantViolation,
433 origin,
434 format!("invariant violation detected ({origin}): {message}"),
435 )
436 }
437
438 pub(crate) fn index_plan_store_invariant(message: impl Into<String>) -> Self {
440 Self::index_plan_invariant(ErrorOrigin::Store, message)
441 }
442
443 pub(crate) fn index_violation(path: &str, index_fields: &[&str]) -> Self {
445 Self::new(
446 ErrorClass::Conflict,
447 ErrorOrigin::Index,
448 format!(
449 "index constraint violation: {path} ({})",
450 index_fields.join(", ")
451 ),
452 )
453 }
454
455 pub(crate) fn from_cursor_plan_error(err: CursorPlanError) -> Self {
461 match err {
462 CursorPlanError::ContinuationCursorInvariantViolation { reason } => {
463 Self::cursor_invariant(reason)
464 }
465 CursorPlanError::InvalidContinuationCursor { .. }
466 | CursorPlanError::InvalidContinuationCursorPayload { .. }
467 | CursorPlanError::ContinuationCursorVersionMismatch { .. }
468 | CursorPlanError::ContinuationCursorSignatureMismatch { .. }
469 | CursorPlanError::ContinuationCursorBoundaryArityMismatch { .. }
470 | CursorPlanError::ContinuationCursorWindowMismatch { .. }
471 | CursorPlanError::ContinuationCursorBoundaryTypeMismatch { .. }
472 | CursorPlanError::ContinuationCursorPrimaryKeyTypeMismatch { .. } => {
473 Self::cursor_unsupported(err.to_string())
474 }
475 }
476 }
477
478 #[cfg(test)]
480 pub(crate) fn from_group_plan_error(err: PlanError) -> Self {
481 let message = match err {
482 PlanError::User(inner) => match *inner {
483 PlanUserError::Group(inner) => {
484 Self::invalid_logical_plan_message(inner.to_string())
485 }
486 other => {
487 format!("group-plan error conversion received non-group user variant: {other}")
488 }
489 },
490 PlanError::Policy(inner) => match *inner {
491 PlanPolicyError::Group(inner) => {
492 Self::invalid_logical_plan_message(inner.to_string())
493 }
494 PlanPolicyError::Policy(inner) => format!(
495 "group-plan error conversion received non-group policy variant: {inner}"
496 ),
497 },
498 PlanError::Cursor(inner) => {
499 format!("group-plan error conversion received cursor variant: {inner}")
500 }
501 };
502
503 Self::planner_invariant(message)
504 }
505
506 pub(crate) fn from_executor_access_plan_error(err: AccessPlanError) -> Self {
508 Self::query_invariant(err.to_string())
509 }
510
511 #[cfg(test)]
514 pub(crate) fn plan_invariant_violation(err: PolicyPlanError) -> Self {
515 let reason = match err {
516 PolicyPlanError::EmptyOrderSpec => {
517 "order specification must include at least one field"
518 }
519 PolicyPlanError::DeletePlanWithOffset => "delete plans must not include OFFSET",
520 PolicyPlanError::DeletePlanWithGrouping => {
521 "delete plans must not include GROUP BY or HAVING"
522 }
523 PolicyPlanError::DeletePlanWithPagination => "delete plans must not include pagination",
524 PolicyPlanError::LoadPlanWithDeleteLimit => "load plans must not carry delete limits",
525 PolicyPlanError::DeleteLimitRequiresOrder => "delete limit requires explicit ordering",
526 PolicyPlanError::UnorderedPagination => "pagination requires explicit ordering",
527 };
528
529 Self::planner_invariant(Self::executor_invariant_message(reason))
530 }
531}
532
533#[derive(Debug, ThisError)]
541pub enum ErrorDetail {
542 #[error("{0}")]
543 Store(StoreError),
544 #[error("{0}")]
545 ViewPatch(crate::patch::MergePatchError),
546 }
556
557impl From<MergePatchError> for InternalError {
558 fn from(err: MergePatchError) -> Self {
559 Self {
560 class: ErrorClass::Unsupported,
561 origin: ErrorOrigin::Interface,
562 message: err.to_string(),
563 detail: Some(ErrorDetail::ViewPatch(err)),
564 }
565 }
566}
567
568#[derive(Debug, ThisError)]
576pub enum StoreError {
577 #[error("key not found: {key}")]
578 NotFound { key: String },
579
580 #[error("store corruption: {message}")]
581 Corrupt { message: String },
582
583 #[error("store invariant violation: {message}")]
584 InvariantViolation { message: String },
585}
586
587#[derive(Clone, Copy, Debug, Eq, PartialEq)]
594pub enum ErrorClass {
595 Corruption,
596 NotFound,
597 Internal,
598 Conflict,
599 Unsupported,
600 InvariantViolation,
601}
602
603impl fmt::Display for ErrorClass {
604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605 let label = match self {
606 Self::Corruption => "corruption",
607 Self::NotFound => "not_found",
608 Self::Internal => "internal",
609 Self::Conflict => "conflict",
610 Self::Unsupported => "unsupported",
611 Self::InvariantViolation => "invariant_violation",
612 };
613 write!(f, "{label}")
614 }
615}
616
617#[derive(Clone, Copy, Debug, Eq, PartialEq)]
624pub enum ErrorOrigin {
625 Serialize,
626 Store,
627 Index,
628 Identity,
629 Query,
630 Planner,
631 Cursor,
632 Recovery,
633 Response,
634 Executor,
635 Interface,
636}
637
638impl fmt::Display for ErrorOrigin {
639 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640 let label = match self {
641 Self::Serialize => "serialize",
642 Self::Store => "store",
643 Self::Index => "index",
644 Self::Identity => "identity",
645 Self::Query => "query",
646 Self::Planner => "planner",
647 Self::Cursor => "cursor",
648 Self::Recovery => "recovery",
649 Self::Response => "response",
650 Self::Executor => "executor",
651 Self::Interface => "interface",
652 };
653 write!(f, "{label}")
654 }
655}