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 #[error("policy: {0}")]
94 Policy(String),
95}
96
97impl OmniError {
98 pub fn manifest(message: impl Into<String>) -> Self {
99 Self::Manifest(ManifestError::new(ManifestErrorKind::BadRequest, message))
100 }
101
102 pub fn manifest_not_found(message: impl Into<String>) -> Self {
103 Self::Manifest(ManifestError::new(ManifestErrorKind::NotFound, message))
104 }
105
106 pub fn manifest_conflict(message: impl Into<String>) -> Self {
107 Self::Manifest(ManifestError::new(ManifestErrorKind::Conflict, message))
108 }
109
110 pub fn manifest_internal(message: impl Into<String>) -> Self {
111 Self::Manifest(ManifestError::new(ManifestErrorKind::Internal, message))
112 }
113
114 pub fn manifest_expected_version_mismatch(
115 table_key: impl Into<String>,
116 expected: u64,
117 actual: u64,
118 ) -> Self {
119 let table_key = table_key.into();
120 let message = format!(
121 "stale view of '{}': expected manifest table version {} but current is {} — refresh and retry",
122 table_key, expected, actual
123 );
124 Self::Manifest(
125 ManifestError::new(ManifestErrorKind::Conflict, message).with_details(
126 ManifestConflictDetails::ExpectedVersionMismatch {
127 table_key,
128 expected,
129 actual,
130 },
131 ),
132 )
133 }
134
135 pub fn manifest_row_level_cas_contention(message: impl Into<String>) -> Self {
136 Self::Manifest(
137 ManifestError::new(ManifestErrorKind::Conflict, message)
138 .with_details(ManifestConflictDetails::RowLevelCasContention),
139 )
140 }
141}