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 #[error("graph already initialized at '{uri}'; pass --force to overwrite")]
102 AlreadyInitialized { uri: String },
103}
104
105impl OmniError {
106 pub fn manifest(message: impl Into<String>) -> Self {
107 Self::Manifest(ManifestError::new(ManifestErrorKind::BadRequest, message))
108 }
109
110 pub fn manifest_not_found(message: impl Into<String>) -> Self {
111 Self::Manifest(ManifestError::new(ManifestErrorKind::NotFound, message))
112 }
113
114 pub fn manifest_conflict(message: impl Into<String>) -> Self {
115 Self::Manifest(ManifestError::new(ManifestErrorKind::Conflict, message))
116 }
117
118 pub fn manifest_internal(message: impl Into<String>) -> Self {
119 Self::Manifest(ManifestError::new(ManifestErrorKind::Internal, message))
120 }
121
122 pub fn manifest_expected_version_mismatch(
123 table_key: impl Into<String>,
124 expected: u64,
125 actual: u64,
126 ) -> Self {
127 let table_key = table_key.into();
128 let message = format!(
129 "stale view of '{}': expected manifest table version {} but current is {} — refresh and retry",
130 table_key, expected, actual
131 );
132 Self::Manifest(
133 ManifestError::new(ManifestErrorKind::Conflict, message).with_details(
134 ManifestConflictDetails::ExpectedVersionMismatch {
135 table_key,
136 expected,
137 actual,
138 },
139 ),
140 )
141 }
142
143 pub fn manifest_row_level_cas_contention(message: impl Into<String>) -> Self {
144 Self::Manifest(
145 ManifestError::new(ManifestErrorKind::Conflict, message)
146 .with_details(ManifestConflictDetails::RowLevelCasContention),
147 )
148 }
149}