ankurah_core/
error.rs

1use std::{collections::BTreeSet, convert::Infallible};
2
3use ankurah_proto::{CollectionId, DecodeError, EntityId, EventId};
4use thiserror::Error;
5
6use crate::{connector::SendError, policy::AccessDenied};
7
8#[derive(Error, Debug)]
9#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
10#[cfg_attr(feature = "uniffi", uniffi(flat_error))]
11pub enum RetrievalError {
12    #[error("access denied")]
13    AccessDenied(AccessDenied),
14    #[error("Parse error: {0}")]
15    ParseError(ankql::error::ParseError),
16    #[error("Entity not found: {0:?}")]
17    EntityNotFound(EntityId),
18    #[error("Event not found: {0:?}")]
19    EventNotFound(EventId),
20    #[error("Storage error: {0}")]
21    StorageError(Box<dyn std::error::Error + Send + Sync + 'static>),
22    #[error("Collection not found: {0}")]
23    CollectionNotFound(CollectionId),
24    #[error("Update failed: {0}")]
25    FailedUpdate(Box<dyn std::error::Error + Send + Sync + 'static>),
26    #[error("Deserialization error: {0}")]
27    DeserializationError(bincode::Error),
28    #[error("No durable peers available for fetch operation")]
29    NoDurablePeers,
30    #[error("Other error: {0}")]
31    Other(String),
32    #[error("bucket name must only contain valid characters")]
33    InvalidBucketName,
34    #[error("ankql filter: {0}")]
35    AnkqlFilter(crate::selection::filter::Error),
36    #[error("Future join: {0}")]
37    FutureJoin(tokio::task::JoinError),
38    #[error("{0}")]
39    Anyhow(anyhow::Error),
40    #[error("Decode error: {0}")]
41    DecodeError(DecodeError),
42    #[error("State error: {0}")]
43    StateError(StateError),
44    #[error("Mutation error: {0}")]
45    MutationError(Box<MutationError>),
46    #[error("Property error: {0}")]
47    PropertyError(Box<crate::property::PropertyError>),
48    #[error("Request error: {0}")]
49    RequestError(RequestError),
50    #[error("Apply error: {0}")]
51    ApplyError(ApplyError),
52}
53
54impl From<RequestError> for RetrievalError {
55    fn from(err: RequestError) -> Self { RetrievalError::RequestError(err) }
56}
57
58impl From<crate::property::PropertyError> for RetrievalError {
59    fn from(err: crate::property::PropertyError) -> Self { RetrievalError::PropertyError(Box::new(err)) }
60}
61
62impl From<tokio::task::JoinError> for RetrievalError {
63    fn from(err: tokio::task::JoinError) -> Self { RetrievalError::FutureJoin(err) }
64}
65
66impl From<MutationError> for RetrievalError {
67    fn from(err: MutationError) -> Self { RetrievalError::MutationError(Box::new(err)) }
68}
69
70impl RetrievalError {
71    pub fn storage(err: impl std::error::Error + Send + Sync + 'static) -> Self { RetrievalError::StorageError(Box::new(err)) }
72}
73
74impl From<bincode::Error> for RetrievalError {
75    fn from(e: bincode::Error) -> Self { RetrievalError::DeserializationError(e) }
76}
77
78impl From<crate::selection::filter::Error> for RetrievalError {
79    fn from(err: crate::selection::filter::Error) -> Self { RetrievalError::AnkqlFilter(err) }
80}
81
82impl From<anyhow::Error> for RetrievalError {
83    fn from(err: anyhow::Error) -> Self { RetrievalError::Anyhow(err) }
84}
85
86impl From<Infallible> for RetrievalError {
87    fn from(_: Infallible) -> Self { unreachable!("Infallible can never be constructed") }
88}
89
90#[derive(Error, Debug)]
91pub enum RequestError {
92    #[error("Peer not connected")]
93    PeerNotConnected,
94    #[error("Connection lost")]
95    ConnectionLost,
96    #[error("Server error: {0}")]
97    ServerError(String),
98    #[error("Send error: {0}")]
99    SendError(SendError),
100    #[error("Internal channel closed")]
101    InternalChannelClosed,
102    #[error("Unexpected response: {0:?}")]
103    UnexpectedResponse(ankurah_proto::NodeResponseBody),
104    #[error("Access denied: {0}")]
105    AccessDenied(AccessDenied),
106}
107
108impl From<AccessDenied> for RequestError {
109    fn from(err: AccessDenied) -> Self { RequestError::AccessDenied(err) }
110}
111
112impl From<SendError> for RequestError {
113    fn from(err: SendError) -> Self { RequestError::SendError(err) }
114}
115
116#[derive(Error, Debug)]
117pub enum SubscriptionError {
118    #[error("predicate not found")]
119    PredicateNotFound,
120    #[error("already subscribed to predicate")]
121    PredicateAlreadySubscribed,
122    #[error("subscription not found")]
123    SubscriptionNotFound,
124}
125
126impl From<DecodeError> for RetrievalError {
127    fn from(err: DecodeError) -> Self { RetrievalError::DecodeError(err) }
128}
129
130#[derive(Error, Debug)]
131#[cfg_attr(feature = "uniffi", derive(uniffi::Error))]
132#[cfg_attr(feature = "uniffi", uniffi(flat_error))]
133pub enum MutationError {
134    #[error("access denied")]
135    AccessDenied(AccessDenied),
136    #[error("already exists")]
137    AlreadyExists,
138    #[error("retrieval error: {0}")]
139    RetrievalError(RetrievalError),
140    #[error("state error: {0}")]
141    StateError(StateError),
142    #[error("failed update: {0}")]
143    UpdateFailed(Box<dyn std::error::Error + Send + Sync + 'static>),
144    #[error("failed step: {0}: {1}")]
145    FailedStep(&'static str, String),
146    #[error("failed to set property: {0}: {1}")]
147    FailedToSetProperty(&'static str, String),
148    #[error("general error: {0}")]
149    General(Box<dyn std::error::Error + Send + Sync + 'static>),
150    #[error("no durable peers available")]
151    NoDurablePeers,
152    #[error("decode error: {0}")]
153    DecodeError(DecodeError),
154    #[error("lineage error: {0}")]
155    LineageError(LineageError),
156    #[error("peer rejected transaction")]
157    PeerRejected,
158    #[error("invalid event")]
159    InvalidEvent,
160    #[error("invalid update")]
161    InvalidUpdate(&'static str),
162    #[error("property error: {0}")]
163    PropertyError(crate::property::PropertyError),
164    #[error("future join: {0}")]
165    FutureJoin(tokio::task::JoinError),
166    #[error("anyhow error: {0}")]
167    Anyhow(anyhow::Error),
168    #[error("TOCTOU attempts exhausted")]
169    TOCTOUAttemptsExhausted,
170}
171
172impl From<tokio::task::JoinError> for MutationError {
173    fn from(err: tokio::task::JoinError) -> Self { MutationError::FutureJoin(err) }
174}
175
176impl From<anyhow::Error> for MutationError {
177    fn from(err: anyhow::Error) -> Self { MutationError::Anyhow(err) }
178}
179
180#[derive(Debug)]
181pub enum LineageError {
182    Incomparable,
183    PartiallyDescends { meet: Vec<EventId> },
184    BudgetExceeded { original_budget: usize, subject_frontier: BTreeSet<EventId>, other_frontier: BTreeSet<EventId> },
185}
186
187impl std::fmt::Display for LineageError {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        match self {
190            LineageError::Incomparable => write!(f, "incomparable"),
191            LineageError::PartiallyDescends { meet } => {
192                write!(f, "partially descends: [")?;
193                let meets: Vec<_> = meet.iter().map(|id| id.to_base64_short()).collect();
194                write!(f, "{}]", meets.join(", "))
195            }
196            LineageError::BudgetExceeded { original_budget, subject_frontier, other_frontier } => {
197                let subject: Vec<_> = subject_frontier.iter().map(|id| id.to_base64_short()).collect();
198                let other: Vec<_> = other_frontier.iter().map(|id| id.to_base64_short()).collect();
199                write!(f, "budget exceeded ({}): subject[{}] other[{}]", original_budget, subject.join(", "), other.join(", "))
200            }
201        }
202    }
203}
204
205impl std::error::Error for LineageError {}
206
207impl From<LineageError> for MutationError {
208    fn from(err: LineageError) -> Self { MutationError::LineageError(err) }
209}
210
211impl From<DecodeError> for MutationError {
212    fn from(err: DecodeError) -> Self { MutationError::DecodeError(err) }
213}
214
215#[cfg(feature = "wasm")]
216impl From<MutationError> for wasm_bindgen::JsValue {
217    fn from(err: MutationError) -> Self { err.to_string().into() }
218}
219#[cfg(feature = "wasm")]
220impl From<RetrievalError> for wasm_bindgen::JsValue {
221    fn from(err: RetrievalError) -> Self { err.to_string().into() }
222}
223
224impl From<AccessDenied> for MutationError {
225    fn from(err: AccessDenied) -> Self { MutationError::AccessDenied(err) }
226}
227
228impl From<bincode::Error> for MutationError {
229    fn from(e: bincode::Error) -> Self { MutationError::StateError(StateError::SerializationError(e)) }
230}
231
232impl From<RetrievalError> for MutationError {
233    fn from(err: RetrievalError) -> Self {
234        match err {
235            RetrievalError::AccessDenied(a) => MutationError::AccessDenied(a),
236            _ => MutationError::RetrievalError(err),
237        }
238    }
239}
240impl From<AccessDenied> for RetrievalError {
241    fn from(err: AccessDenied) -> Self { RetrievalError::AccessDenied(err) }
242}
243
244impl From<SubscriptionError> for RetrievalError {
245    fn from(err: SubscriptionError) -> Self { RetrievalError::Anyhow(anyhow::anyhow!("Subscription error: {:?}", err)) }
246}
247
248#[derive(Error, Debug)]
249pub enum StateError {
250    #[error("serialization error: {0}")]
251    SerializationError(Box<dyn std::error::Error + Send + Sync + 'static>),
252    #[error("DDL error: {0}")]
253    DDLError(Box<dyn std::error::Error + Send + Sync + 'static>),
254    #[error("DMLError: {0}")]
255    DMLError(Box<dyn std::error::Error + Send + Sync + 'static>),
256}
257
258impl From<bincode::Error> for StateError {
259    fn from(e: bincode::Error) -> Self { StateError::SerializationError(Box::new(e)) }
260}
261
262impl From<StateError> for MutationError {
263    fn from(err: StateError) -> Self { MutationError::StateError(err) }
264}
265
266impl From<crate::property::PropertyError> for MutationError {
267    fn from(err: crate::property::PropertyError) -> Self { MutationError::PropertyError(err) }
268}
269
270impl From<StateError> for RetrievalError {
271    fn from(err: StateError) -> Self { RetrievalError::StateError(err) }
272}
273
274#[derive(Error, Debug)]
275pub enum ValidationError {
276    #[error("Deserialization error: {0}")]
277    Deserialization(Box<dyn std::error::Error + Send + Sync + 'static>),
278    #[error("Validation failed: {0}")]
279    ValidationFailed(String),
280    #[error("Serialization error: {0}")]
281    Serialization(String),
282    #[error("Rejected: {0}")]
283    Rejected(&'static str),
284}
285
286/// Error type for NodeApplier operations (applying remote deltas)
287#[derive(Debug)]
288pub enum ApplyError {
289    Items(Vec<ApplyErrorItem>),
290    CollectionNotFound(CollectionId),
291    RetrievalError(Box<RetrievalError>),
292    MutationError(Box<MutationError>),
293}
294
295impl std::fmt::Display for ApplyError {
296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297        match self {
298            ApplyError::Items(errors) => {
299                write!(f, "Failed to apply {} delta(s)", errors.len())?;
300                for (i, err) in errors.iter().enumerate() {
301                    write!(f, "\n  [{}] {}", i + 1, err)?;
302                }
303                Ok(())
304            }
305            ApplyError::CollectionNotFound(id) => write!(f, "Collection not found: {}", id),
306            ApplyError::RetrievalError(e) => write!(f, "Retrieval error: {}", e),
307            ApplyError::MutationError(e) => write!(f, "Mutation error: {}", e),
308        }
309    }
310}
311
312impl std::error::Error for ApplyError {
313    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
314        match self {
315            ApplyError::RetrievalError(e) => Some(e),
316            ApplyError::MutationError(e) => Some(e),
317            _ => None,
318        }
319    }
320}
321
322/// Error applying a specific delta
323#[derive(Debug)]
324pub struct ApplyErrorItem {
325    pub entity_id: EntityId,
326    pub collection: CollectionId,
327    pub cause: MutationError,
328}
329
330impl std::fmt::Display for ApplyErrorItem {
331    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332        write!(f, "Failed to apply delta for entity {} in collection {}: {}", self.entity_id.to_base64_short(), self.collection, self.cause)
333    }
334}
335
336impl From<RetrievalError> for ApplyError {
337    fn from(err: RetrievalError) -> Self { ApplyError::RetrievalError(Box::new(err)) }
338}
339
340impl From<MutationError> for ApplyError {
341    fn from(err: MutationError) -> Self { ApplyError::MutationError(Box::new(err)) }
342}
343
344impl From<ApplyError> for RetrievalError {
345    fn from(err: ApplyError) -> Self { RetrievalError::ApplyError(err) }
346}