1use thiserror::Error;
2
3pub type Result<T> = std::result::Result<T, OmniError>;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ManifestErrorKind {
7 BadRequest,
8 NotFound,
9 Conflict,
10 Internal,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum ManifestConflictDetails {
18 ExpectedVersionMismatch {
21 table_key: String,
22 expected: u64,
23 actual: u64,
24 },
25 RowLevelCasContention,
30}
31
32#[derive(Debug, Clone, Error)]
33#[error("{message}")]
34pub struct ManifestError {
35 pub kind: ManifestErrorKind,
36 pub message: String,
37 pub details: Option<ManifestConflictDetails>,
38}
39
40impl ManifestError {
41 pub fn new(kind: ManifestErrorKind, message: impl Into<String>) -> Self {
42 Self {
43 kind,
44 message: message.into(),
45 details: None,
46 }
47 }
48
49 pub fn with_details(mut self, details: ManifestConflictDetails) -> Self {
50 self.details = Some(details);
51 self
52 }
53}
54
55#[derive(Debug, Clone)]
56pub struct MergeConflict {
57 pub table_key: String,
58 pub row_id: Option<String>,
59 pub kind: MergeConflictKind,
60 pub message: String,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum MergeConflictKind {
65 DivergentInsert,
66 DivergentUpdate,
67 DeleteVsUpdate,
68 OrphanEdge,
69 UniqueViolation,
70 CardinalityViolation,
71 ValueConstraintViolation,
72}
73
74#[derive(Debug, Error)]
75pub enum OmniError {
76 #[error("{0}")]
77 Compiler(#[from] omnigraph_compiler::error::NanoError),
78 #[error("storage: {0}")]
79 Lance(String),
80 #[error("query: {0}")]
81 DataFusion(String),
82 #[error("io: {0}")]
83 Io(#[from] std::io::Error),
84 #[error("{0}")]
85 Manifest(ManifestError),
86 #[error("merge conflicts: {0:?}")]
87 MergeConflicts(Vec<MergeConflict>),
88}
89
90impl OmniError {
91 pub fn manifest(message: impl Into<String>) -> Self {
92 Self::Manifest(ManifestError::new(ManifestErrorKind::BadRequest, message))
93 }
94
95 pub fn manifest_not_found(message: impl Into<String>) -> Self {
96 Self::Manifest(ManifestError::new(ManifestErrorKind::NotFound, message))
97 }
98
99 pub fn manifest_conflict(message: impl Into<String>) -> Self {
100 Self::Manifest(ManifestError::new(ManifestErrorKind::Conflict, message))
101 }
102
103 pub fn manifest_internal(message: impl Into<String>) -> Self {
104 Self::Manifest(ManifestError::new(ManifestErrorKind::Internal, message))
105 }
106
107 pub fn manifest_expected_version_mismatch(
108 table_key: impl Into<String>,
109 expected: u64,
110 actual: u64,
111 ) -> Self {
112 let table_key = table_key.into();
113 let message = format!(
114 "stale view of '{}': expected manifest table version {} but current is {} — refresh and retry",
115 table_key, expected, actual
116 );
117 Self::Manifest(
118 ManifestError::new(ManifestErrorKind::Conflict, message).with_details(
119 ManifestConflictDetails::ExpectedVersionMismatch {
120 table_key,
121 expected,
122 actual,
123 },
124 ),
125 )
126 }
127
128 pub fn manifest_row_level_cas_contention(message: impl Into<String>) -> Self {
129 Self::Manifest(
130 ManifestError::new(ManifestErrorKind::Conflict, message)
131 .with_details(ManifestConflictDetails::RowLevelCasContention),
132 )
133 }
134}