Skip to main content

omnigraph/
error.rs

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/// Structured details for a manifest-level conflict. Set on the `details`
14/// field of `ManifestError` when callers need to match on the specific
15/// concurrency-control failure rather than parse a string.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum ManifestConflictDetails {
18    /// A caller-supplied per-table expected version did not match the
19    /// manifest's current latest non-tombstoned version for that table.
20    ExpectedVersionMismatch {
21        table_key: String,
22        expected: u64,
23        actual: u64,
24    },
25    /// Lance's row-level CAS rejected the publish because a concurrent writer
26    /// landed a row with the same `object_id`. Distinct from
27    /// `ExpectedVersionMismatch`: the caller's expectations (if any) still
28    /// hold against the new manifest state, so the publisher will retry.
29    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}