Skip to main content

stateset_sync/
error.rs

1use thiserror::Error;
2
3/// Errors that can occur during sync operations.
4#[derive(Debug, Error)]
5#[non_exhaustive]
6pub enum SyncError {
7    /// The outbox has reached its maximum capacity.
8    #[error("outbox full: capacity {capacity}, current {current}")]
9    OutboxFull {
10        /// Maximum allowed events.
11        capacity: usize,
12        /// Current event count.
13        current: usize,
14    },
15
16    /// The event buffer has reached its maximum capacity (informational; old events are evicted).
17    #[error("buffer full: capacity {0}")]
18    BufferFull(usize),
19
20    /// A transport-level error occurred during push or pull.
21    #[error("transport error: {0}")]
22    Transport(String),
23
24    /// A conflict was detected between local and remote events.
25    #[error("conflict on entity {entity_type}/{entity_id}: {description}")]
26    Conflict {
27        /// The type of entity involved.
28        entity_type: String,
29        /// The identifier of the entity.
30        entity_id: String,
31        /// Human-readable conflict description.
32        description: String,
33    },
34
35    /// The sync engine has not been initialized.
36    #[error("sync engine not initialized")]
37    NotInitialized,
38
39    /// A serialization or deserialization error occurred.
40    #[error("serialization error: {0}")]
41    Serialization(String),
42
43    /// A local storage operation failed (for durable outbox persistence).
44    #[error("storage error: {0}")]
45    Storage(String),
46
47    /// An invalid configuration value was provided.
48    #[error("invalid config: {0}")]
49    InvalidConfig(String),
50
51    /// An event with a duplicate ID was detected.
52    #[error("duplicate event id: {0}")]
53    DuplicateEvent(String),
54
55    /// The requested sequence number is out of range.
56    #[error("sequence {requested} is out of range (head: {head})")]
57    SequenceOutOfRange {
58        /// The sequence that was requested.
59        requested: u64,
60        /// The current head sequence.
61        head: u64,
62    },
63}
64
65impl From<serde_json::Error> for SyncError {
66    fn from(err: serde_json::Error) -> Self {
67        Self::Serialization(err.to_string())
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn error_display_outbox_full() {
77        let err = SyncError::OutboxFull { capacity: 1000, current: 1000 };
78        assert_eq!(err.to_string(), "outbox full: capacity 1000, current 1000");
79    }
80
81    #[test]
82    fn error_display_transport() {
83        let err = SyncError::Transport("connection refused".into());
84        assert_eq!(err.to_string(), "transport error: connection refused");
85    }
86
87    #[test]
88    fn error_display_conflict() {
89        let err = SyncError::Conflict {
90            entity_type: "order".into(),
91            entity_id: "ORD-123".into(),
92            description: "version mismatch".into(),
93        };
94        assert!(err.to_string().contains("order/ORD-123"));
95    }
96
97    #[test]
98    fn error_from_serde_json() {
99        let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
100        let sync_err: SyncError = json_err.into();
101        assert!(matches!(sync_err, SyncError::Serialization(_)));
102    }
103
104    #[test]
105    fn error_display_storage() {
106        let err = SyncError::Storage("disk full".into());
107        assert_eq!(err.to_string(), "storage error: disk full");
108    }
109
110    #[test]
111    fn error_display_sequence_out_of_range() {
112        let err = SyncError::SequenceOutOfRange { requested: 500, head: 100 };
113        assert!(err.to_string().contains("500"));
114        assert!(err.to_string().contains("100"));
115    }
116}