1#[cfg(test)]
8mod tests;
9
10use crate::serialize::{SerializeError, SerializeErrorKind};
11use std::fmt;
12use thiserror::Error as ThisError;
13
14#[derive(Debug, ThisError)]
115#[error("{message}")]
116pub struct InternalError {
117 pub(crate) class: ErrorClass,
118 pub(crate) origin: ErrorOrigin,
119 pub(crate) message: String,
120
121 pub(crate) detail: Option<ErrorDetail>,
124}
125
126impl InternalError {
127 pub fn new(class: ErrorClass, origin: ErrorOrigin, message: impl Into<String>) -> Self {
131 let message = message.into();
132
133 let detail = match (class, origin) {
134 (ErrorClass::Corruption, ErrorOrigin::Store) => {
135 Some(ErrorDetail::Store(StoreError::Corrupt {
136 message: message.clone(),
137 }))
138 }
139 (ErrorClass::InvariantViolation, ErrorOrigin::Store) => {
140 Some(ErrorDetail::Store(StoreError::InvariantViolation {
141 message: message.clone(),
142 }))
143 }
144 _ => None,
145 };
146
147 Self {
148 class,
149 origin,
150 message,
151 detail,
152 }
153 }
154
155 #[must_use]
157 pub const fn class(&self) -> ErrorClass {
158 self.class
159 }
160
161 #[must_use]
163 pub const fn origin(&self) -> ErrorOrigin {
164 self.origin
165 }
166
167 #[must_use]
169 pub fn message(&self) -> &str {
170 &self.message
171 }
172
173 #[must_use]
175 pub const fn detail(&self) -> Option<&ErrorDetail> {
176 self.detail.as_ref()
177 }
178
179 #[must_use]
181 pub fn into_message(self) -> String {
182 self.message
183 }
184
185 pub(crate) fn classified(
187 class: ErrorClass,
188 origin: ErrorOrigin,
189 message: impl Into<String>,
190 ) -> Self {
191 Self::new(class, origin, message)
192 }
193
194 pub(crate) fn with_message(self, message: impl Into<String>) -> Self {
196 Self::classified(self.class, self.origin, message)
197 }
198
199 pub(crate) fn with_origin(self, origin: ErrorOrigin) -> Self {
203 Self::classified(self.class, origin, self.message)
204 }
205
206 pub(crate) fn index_invariant(message: impl Into<String>) -> Self {
208 Self::new(
209 ErrorClass::InvariantViolation,
210 ErrorOrigin::Index,
211 message.into(),
212 )
213 }
214
215 pub(crate) fn planner_executor_invariant(reason: impl Into<String>) -> Self {
218 Self::new(
219 ErrorClass::InvariantViolation,
220 ErrorOrigin::Planner,
221 Self::executor_invariant_message(reason),
222 )
223 }
224
225 pub(crate) fn query_executor_invariant(reason: impl Into<String>) -> Self {
228 Self::new(
229 ErrorClass::InvariantViolation,
230 ErrorOrigin::Query,
231 Self::executor_invariant_message(reason),
232 )
233 }
234
235 pub(crate) fn cursor_executor_invariant(reason: impl Into<String>) -> Self {
238 Self::new(
239 ErrorClass::InvariantViolation,
240 ErrorOrigin::Cursor,
241 Self::executor_invariant_message(reason),
242 )
243 }
244
245 pub(crate) fn executor_invariant(message: impl Into<String>) -> Self {
247 Self::new(
248 ErrorClass::InvariantViolation,
249 ErrorOrigin::Executor,
250 message.into(),
251 )
252 }
253
254 pub(crate) fn executor_internal(message: impl Into<String>) -> Self {
256 Self::new(ErrorClass::Internal, ErrorOrigin::Executor, message.into())
257 }
258
259 pub(crate) fn executor_unsupported(message: impl Into<String>) -> Self {
261 Self::new(
262 ErrorClass::Unsupported,
263 ErrorOrigin::Executor,
264 message.into(),
265 )
266 }
267
268 pub(crate) fn mutation_entity_schema_invalid(
270 entity_path: &str,
271 detail: impl fmt::Display,
272 ) -> Self {
273 Self::executor_invariant(format!("entity schema invalid for {entity_path}: {detail}"))
274 }
275
276 pub(crate) fn mutation_entity_primary_key_missing(entity_path: &str, field_name: &str) -> Self {
278 Self::executor_invariant(format!(
279 "entity primary key field missing: {entity_path} field={field_name}",
280 ))
281 }
282
283 pub(crate) fn mutation_entity_primary_key_invalid_value(
285 entity_path: &str,
286 field_name: &str,
287 value: &crate::value::Value,
288 ) -> Self {
289 Self::executor_invariant(format!(
290 "entity primary key field has invalid value: {entity_path} field={field_name} value={value:?}",
291 ))
292 }
293
294 pub(crate) fn mutation_entity_primary_key_type_mismatch(
296 entity_path: &str,
297 field_name: &str,
298 value: &crate::value::Value,
299 ) -> Self {
300 Self::executor_invariant(format!(
301 "entity primary key field type mismatch: {entity_path} field={field_name} value={value:?}",
302 ))
303 }
304
305 pub(crate) fn mutation_entity_primary_key_mismatch(
307 entity_path: &str,
308 field_name: &str,
309 field_value: &crate::value::Value,
310 identity_key: &crate::value::Value,
311 ) -> Self {
312 Self::executor_invariant(format!(
313 "entity primary key mismatch: {entity_path} field={field_name} field_value={field_value:?} id_key={identity_key:?}",
314 ))
315 }
316
317 pub(crate) fn mutation_entity_field_missing(
319 entity_path: &str,
320 field_name: &str,
321 indexed: bool,
322 ) -> Self {
323 let indexed_note = if indexed { " (indexed)" } else { "" };
324
325 Self::executor_invariant(format!(
326 "entity field missing: {entity_path} field={field_name}{indexed_note}",
327 ))
328 }
329
330 pub(crate) fn mutation_entity_field_type_mismatch(
332 entity_path: &str,
333 field_name: &str,
334 value: &crate::value::Value,
335 ) -> Self {
336 Self::executor_invariant(format!(
337 "entity field type mismatch: {entity_path} field={field_name} value={value:?}",
338 ))
339 }
340
341 pub(crate) fn mutation_decimal_scale_mismatch(
343 entity_path: &str,
344 field_name: &str,
345 expected_scale: impl fmt::Display,
346 actual_scale: impl fmt::Display,
347 ) -> Self {
348 Self::executor_unsupported(format!(
349 "decimal field scale mismatch: {entity_path} field={field_name} expected_scale={expected_scale} actual_scale={actual_scale}",
350 ))
351 }
352
353 pub(crate) fn mutation_set_field_list_required(entity_path: &str, field_name: &str) -> Self {
355 Self::executor_invariant(format!(
356 "set field must encode as Value::List: {entity_path} field={field_name}",
357 ))
358 }
359
360 pub(crate) fn mutation_set_field_not_canonical(entity_path: &str, field_name: &str) -> Self {
362 Self::executor_invariant(format!(
363 "set field must be strictly ordered and deduplicated: {entity_path} field={field_name}",
364 ))
365 }
366
367 pub(crate) fn mutation_map_field_map_required(entity_path: &str, field_name: &str) -> Self {
369 Self::executor_invariant(format!(
370 "map field must encode as Value::Map: {entity_path} field={field_name}",
371 ))
372 }
373
374 pub(crate) fn mutation_map_field_entries_invalid(
376 entity_path: &str,
377 field_name: &str,
378 detail: impl fmt::Display,
379 ) -> Self {
380 Self::executor_invariant(format!(
381 "map field entries violate map invariants: {entity_path} field={field_name} ({detail})",
382 ))
383 }
384
385 pub(crate) fn mutation_map_field_entries_not_canonical(
387 entity_path: &str,
388 field_name: &str,
389 ) -> Self {
390 Self::executor_invariant(format!(
391 "map field entries are not in canonical deterministic order: {entity_path} field={field_name}",
392 ))
393 }
394
395 pub(crate) fn scalar_page_predicate_slots_required() -> Self {
397 Self::query_executor_invariant("post-access filtering requires precompiled predicate slots")
398 }
399
400 pub(crate) fn scalar_page_ordering_after_filtering_required() -> Self {
402 Self::query_executor_invariant("ordering must run after filtering")
403 }
404
405 pub(crate) fn scalar_page_cursor_boundary_order_required() -> Self {
407 Self::query_executor_invariant("cursor boundary requires ordering")
408 }
409
410 pub(crate) fn scalar_page_cursor_boundary_after_ordering_required() -> Self {
412 Self::query_executor_invariant("cursor boundary must run after ordering")
413 }
414
415 pub(crate) fn scalar_page_pagination_after_ordering_required() -> Self {
417 Self::query_executor_invariant("pagination must run after ordering")
418 }
419
420 pub(crate) fn scalar_page_delete_limit_after_ordering_required() -> Self {
422 Self::query_executor_invariant("delete limit must run after ordering")
423 }
424
425 pub(crate) fn load_runtime_scalar_payload_required() -> Self {
427 Self::query_executor_invariant("scalar load mode must carry scalar runtime payload")
428 }
429
430 pub(crate) fn load_runtime_grouped_payload_required() -> Self {
432 Self::query_executor_invariant("grouped load mode must carry grouped runtime payload")
433 }
434
435 pub(crate) fn load_runtime_scalar_surface_payload_required() -> Self {
437 Self::query_executor_invariant("scalar page load mode must carry scalar runtime payload")
438 }
439
440 pub(crate) fn load_runtime_grouped_surface_payload_required() -> Self {
442 Self::query_executor_invariant("grouped page load mode must carry grouped runtime payload")
443 }
444
445 pub(crate) fn load_executor_load_plan_required() -> Self {
447 Self::query_executor_invariant("load executor requires load plans")
448 }
449
450 pub(crate) fn delete_executor_grouped_unsupported() -> Self {
452 Self::executor_unsupported("grouped query execution is not yet enabled in this release")
453 }
454
455 pub(crate) fn delete_executor_delete_plan_required() -> Self {
457 Self::query_executor_invariant("delete executor requires delete plans")
458 }
459
460 pub(crate) fn aggregate_fold_mode_terminal_contract_required() -> Self {
462 Self::query_executor_invariant(
463 "aggregate fold mode must match route fold-mode contract for aggregate terminal",
464 )
465 }
466
467 pub(crate) fn fast_stream_exact_key_count_required() -> Self {
469 Self::query_executor_invariant("fast-path stream must expose an exact key-count hint")
470 }
471
472 pub(crate) fn fast_stream_route_kind_request_match_required() -> Self {
474 Self::query_executor_invariant("fast-stream route kind/request mismatch")
475 }
476
477 pub(crate) fn secondary_index_prefix_spec_required() -> Self {
479 Self::query_executor_invariant(
480 "index-prefix executable spec must be materialized for index-prefix plans",
481 )
482 }
483
484 pub(crate) fn index_range_limit_spec_required() -> Self {
486 Self::query_executor_invariant(
487 "index-range executable spec must be materialized for index-range plans",
488 )
489 }
490
491 pub(crate) fn row_layout_primary_key_slot_required() -> Self {
493 Self::query_executor_invariant("row layout missing primary-key slot")
494 }
495
496 pub(crate) fn mutation_atomic_save_duplicate_key(
498 entity_path: &str,
499 key: impl fmt::Display,
500 ) -> Self {
501 Self::executor_unsupported(format!(
502 "atomic save batch rejected duplicate key: entity={entity_path} key={key}",
503 ))
504 }
505
506 pub(crate) fn mutation_index_store_generation_changed(
508 expected_generation: u64,
509 observed_generation: u64,
510 ) -> Self {
511 Self::executor_invariant(format!(
512 "index store generation changed between preflight and apply: expected {expected_generation}, found {observed_generation}",
513 ))
514 }
515
516 #[must_use]
518 pub(crate) fn executor_invariant_message(reason: impl Into<String>) -> String {
519 format!("executor invariant violated: {}", reason.into())
520 }
521
522 pub(crate) fn planner_invariant(message: impl Into<String>) -> Self {
524 Self::new(
525 ErrorClass::InvariantViolation,
526 ErrorOrigin::Planner,
527 message.into(),
528 )
529 }
530
531 #[must_use]
533 pub(crate) fn invalid_logical_plan_message(reason: impl Into<String>) -> String {
534 format!("invalid logical plan: {}", reason.into())
535 }
536
537 pub(crate) fn query_invalid_logical_plan(reason: impl Into<String>) -> Self {
539 Self::planner_invariant(Self::invalid_logical_plan_message(reason))
540 }
541
542 pub(crate) fn query_invariant(message: impl Into<String>) -> Self {
544 Self::new(
545 ErrorClass::InvariantViolation,
546 ErrorOrigin::Query,
547 message.into(),
548 )
549 }
550
551 pub(crate) fn store_invariant(message: impl Into<String>) -> Self {
553 Self::new(
554 ErrorClass::InvariantViolation,
555 ErrorOrigin::Store,
556 message.into(),
557 )
558 }
559
560 pub(crate) fn store_internal(message: impl Into<String>) -> Self {
562 Self::new(ErrorClass::Internal, ErrorOrigin::Store, message.into())
563 }
564
565 pub(crate) fn index_internal(message: impl Into<String>) -> Self {
567 Self::new(ErrorClass::Internal, ErrorOrigin::Index, message.into())
568 }
569
570 #[cfg(test)]
572 pub(crate) fn query_internal(message: impl Into<String>) -> Self {
573 Self::new(ErrorClass::Internal, ErrorOrigin::Query, message.into())
574 }
575
576 pub(crate) fn query_unsupported(message: impl Into<String>) -> Self {
578 Self::new(ErrorClass::Unsupported, ErrorOrigin::Query, message.into())
579 }
580
581 pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
583 Self::new(ErrorClass::Internal, ErrorOrigin::Serialize, message.into())
584 }
585
586 pub(crate) fn store_corruption(message: impl Into<String>) -> Self {
588 Self::new(ErrorClass::Corruption, ErrorOrigin::Store, message.into())
589 }
590
591 pub(crate) fn commit_corruption(detail: impl fmt::Display) -> Self {
593 Self::store_corruption(format!("commit marker corrupted: {detail}"))
594 }
595
596 pub(crate) fn commit_component_corruption(component: &str, detail: impl fmt::Display) -> Self {
598 Self::store_corruption(format!("commit marker {component} corrupted: {detail}"))
599 }
600
601 pub(crate) fn index_corruption(message: impl Into<String>) -> Self {
603 Self::new(ErrorClass::Corruption, ErrorOrigin::Index, message.into())
604 }
605
606 pub(crate) fn serialize_corruption(message: impl Into<String>) -> Self {
608 Self::new(
609 ErrorClass::Corruption,
610 ErrorOrigin::Serialize,
611 message.into(),
612 )
613 }
614
615 #[must_use]
617 pub fn missing_persisted_slot(field_name: &'static str) -> Self {
618 Self::serialize_corruption(format!(
619 "row decode failed: missing required field '{field_name}'",
620 ))
621 }
622
623 pub(crate) fn identity_corruption(message: impl Into<String>) -> Self {
625 Self::new(
626 ErrorClass::Corruption,
627 ErrorOrigin::Identity,
628 message.into(),
629 )
630 }
631
632 pub(crate) fn store_unsupported(message: impl Into<String>) -> Self {
634 Self::new(ErrorClass::Unsupported, ErrorOrigin::Store, message.into())
635 }
636
637 pub(crate) fn index_unsupported(message: impl Into<String>) -> Self {
639 Self::new(ErrorClass::Unsupported, ErrorOrigin::Index, message.into())
640 }
641
642 pub(crate) fn serialize_unsupported(message: impl Into<String>) -> Self {
644 Self::new(
645 ErrorClass::Unsupported,
646 ErrorOrigin::Serialize,
647 message.into(),
648 )
649 }
650
651 pub(crate) fn cursor_unsupported(message: impl Into<String>) -> Self {
653 Self::new(ErrorClass::Unsupported, ErrorOrigin::Cursor, message.into())
654 }
655
656 pub(crate) fn serialize_incompatible_persisted_format(message: impl Into<String>) -> Self {
658 Self::new(
659 ErrorClass::IncompatiblePersistedFormat,
660 ErrorOrigin::Serialize,
661 message.into(),
662 )
663 }
664
665 pub(crate) fn serialize_payload_decode_failed(
668 source: SerializeError,
669 payload_label: &'static str,
670 ) -> Self {
671 match source {
672 SerializeError::DeserializeSizeLimitExceeded { len, max_bytes } => {
675 Self::serialize_corruption(format!(
676 "{payload_label} decode failed: payload size {len} exceeds limit {max_bytes}"
677 ))
678 }
679 SerializeError::Deserialize(_) => Self::serialize_corruption(format!(
680 "{payload_label} decode failed: {}",
681 SerializeErrorKind::Deserialize
682 )),
683 SerializeError::Serialize(_) => Self::serialize_corruption(format!(
684 "{payload_label} decode failed: {}",
685 SerializeErrorKind::Serialize
686 )),
687 }
688 }
689
690 #[cfg(feature = "sql")]
693 pub(crate) fn query_unsupported_sql_feature(feature: &'static str) -> Self {
694 let message = format!(
695 "SQL query is not executable in this release: unsupported SQL feature: {feature}"
696 );
697
698 Self {
699 class: ErrorClass::Unsupported,
700 origin: ErrorOrigin::Query,
701 message,
702 detail: Some(ErrorDetail::Query(
703 QueryErrorDetail::UnsupportedSqlFeature { feature },
704 )),
705 }
706 }
707
708 pub fn store_not_found(key: impl Into<String>) -> Self {
709 let key = key.into();
710
711 Self {
712 class: ErrorClass::NotFound,
713 origin: ErrorOrigin::Store,
714 message: format!("data key not found: {key}"),
715 detail: Some(ErrorDetail::Store(StoreError::NotFound { key })),
716 }
717 }
718
719 pub fn unsupported_entity_path(path: impl Into<String>) -> Self {
721 let path = path.into();
722
723 Self::new(
724 ErrorClass::Unsupported,
725 ErrorOrigin::Store,
726 format!("unsupported entity path: '{path}'"),
727 )
728 }
729
730 #[must_use]
731 pub const fn is_not_found(&self) -> bool {
732 matches!(
733 self.detail,
734 Some(ErrorDetail::Store(StoreError::NotFound { .. }))
735 )
736 }
737
738 #[must_use]
739 pub fn display_with_class(&self) -> String {
740 format!("{}:{}: {}", self.origin, self.class, self.message)
741 }
742
743 pub(crate) fn index_plan_corruption(origin: ErrorOrigin, message: impl Into<String>) -> Self {
745 let message = message.into();
746 Self::new(
747 ErrorClass::Corruption,
748 origin,
749 format!("corruption detected ({origin}): {message}"),
750 )
751 }
752
753 pub(crate) fn index_plan_index_corruption(message: impl Into<String>) -> Self {
755 Self::index_plan_corruption(ErrorOrigin::Index, message)
756 }
757
758 pub(crate) fn index_plan_store_corruption(message: impl Into<String>) -> Self {
760 Self::index_plan_corruption(ErrorOrigin::Store, message)
761 }
762
763 pub(crate) fn index_plan_serialize_corruption(message: impl Into<String>) -> Self {
765 Self::index_plan_corruption(ErrorOrigin::Serialize, message)
766 }
767
768 pub(crate) fn index_plan_invariant(origin: ErrorOrigin, message: impl Into<String>) -> Self {
770 let message = message.into();
771 Self::new(
772 ErrorClass::InvariantViolation,
773 origin,
774 format!("invariant violation detected ({origin}): {message}"),
775 )
776 }
777
778 pub(crate) fn index_plan_store_invariant(message: impl Into<String>) -> Self {
780 Self::index_plan_invariant(ErrorOrigin::Store, message)
781 }
782
783 pub(crate) fn index_violation(path: &str, index_fields: &[&str]) -> Self {
785 Self::new(
786 ErrorClass::Conflict,
787 ErrorOrigin::Index,
788 format!(
789 "index constraint violation: {path} ({})",
790 index_fields.join(", ")
791 ),
792 )
793 }
794}
795
796#[derive(Debug, ThisError)]
804pub enum ErrorDetail {
805 #[error("{0}")]
806 Store(StoreError),
807 #[error("{0}")]
808 Query(QueryErrorDetail),
809 }
816
817#[derive(Debug, ThisError)]
825pub enum StoreError {
826 #[error("key not found: {key}")]
827 NotFound { key: String },
828
829 #[error("store corruption: {message}")]
830 Corrupt { message: String },
831
832 #[error("store invariant violation: {message}")]
833 InvariantViolation { message: String },
834}
835
836#[derive(Debug, ThisError)]
843pub enum QueryErrorDetail {
844 #[error("unsupported SQL feature: {feature}")]
845 UnsupportedSqlFeature { feature: &'static str },
846}
847
848#[derive(Clone, Copy, Debug, Eq, PartialEq)]
855pub enum ErrorClass {
856 Corruption,
857 IncompatiblePersistedFormat,
858 NotFound,
859 Internal,
860 Conflict,
861 Unsupported,
862 InvariantViolation,
863}
864
865impl fmt::Display for ErrorClass {
866 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
867 let label = match self {
868 Self::Corruption => "corruption",
869 Self::IncompatiblePersistedFormat => "incompatible_persisted_format",
870 Self::NotFound => "not_found",
871 Self::Internal => "internal",
872 Self::Conflict => "conflict",
873 Self::Unsupported => "unsupported",
874 Self::InvariantViolation => "invariant_violation",
875 };
876 write!(f, "{label}")
877 }
878}
879
880#[derive(Clone, Copy, Debug, Eq, PartialEq)]
887pub enum ErrorOrigin {
888 Serialize,
889 Store,
890 Index,
891 Identity,
892 Query,
893 Planner,
894 Cursor,
895 Recovery,
896 Response,
897 Executor,
898 Interface,
899}
900
901impl fmt::Display for ErrorOrigin {
902 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
903 let label = match self {
904 Self::Serialize => "serialize",
905 Self::Store => "store",
906 Self::Index => "index",
907 Self::Identity => "identity",
908 Self::Query => "query",
909 Self::Planner => "planner",
910 Self::Cursor => "cursor",
911 Self::Recovery => "recovery",
912 Self::Response => "response",
913 Self::Executor => "executor",
914 Self::Interface => "interface",
915 };
916 write!(f, "{label}")
917 }
918}