Skip to main content

noxu_rep/
error.rs

1//! Replication error types.
2//!
3
4use thiserror::Error;
5
6/// Errors that can occur during replication operations.
7#[derive(Debug, Error)]
8pub enum RepError {
9    /// The node is not the master. Thrown when a write operation is attempted
10    /// on a replica node.
11    ///
12    #[error("node is not master: current master is {master:?}")]
13    NotMaster {
14        /// The name of the current master, if known.
15        master: Option<String>,
16    },
17
18    /// The node is not a replica. Thrown when a replica-only operation is
19    /// attempted on the master.
20    #[error("node is not replica")]
21    NotReplica,
22
23    /// The replication group does not exist.
24    /// (group aspect).
25    #[error("replication group does not exist: {0}")]
26    GroupNotFound(String),
27
28    /// A node was not found in the replication group.
29    ///
30    #[error("node {0} not found in group")]
31    NodeNotFound(String),
32
33    /// An election failed to produce a master.
34    ///
35    #[error("election failed: {0}")]
36    ElectionFailed(String),
37
38    /// Insufficient replica acknowledgments for a commit.
39    ///
40    #[error("insufficient acks: needed {needed}, got {received}")]
41    InsufficientAcks {
42        /// Number of acks required by the durability policy.
43        needed: u32,
44        /// Number of acks actually received.
45        received: u32,
46    },
47
48    /// A replica consistency policy timed out.
49    ///
50    #[error("replica consistency timeout after {0:?}")]
51    ConsistencyTimeout(std::time::Duration),
52
53    /// The replica's replication lag exceeds the configured limit.
54    #[error("replica lag too high: {lag_ms}ms exceeds limit {limit_ms}ms")]
55    ReplicaLagExceeded {
56        /// Current lag in milliseconds.
57        lag_ms: u64,
58        /// Configured limit in milliseconds.
59        limit_ms: u64,
60    },
61
62    /// A hard rollback is required on the replica.
63    ///
64    #[error("rollback required: from VLSN {from} to {to}")]
65    RollbackRequired {
66        /// The VLSN sequence to roll back from.
67        from: i64,
68        /// The VLSN sequence to roll back to.
69        to: i64,
70    },
71
72    /// A network-level error occurred.
73    #[error("network error: {0}")]
74    NetworkError(String),
75
76    /// A replication frame failed CRC32 verification.
77    ///
78    /// Indicates in-flight corruption (hardware error, kernel bug, or an
79    /// adversarial sender). The receiver closes the channel when this occurs.
80    #[error(
81        "frame corrupted: vlsn={vlsn} expected_crc={expected:#010x} actual_crc={actual:#010x}"
82    )]
83    FrameCorrupted { vlsn: u64, expected: u32, actual: u32 },
84
85    /// A replication protocol error occurred (unexpected message, version
86    /// mismatch, etc.).
87    #[error("protocol error: {0}")]
88    ProtocolError(String),
89
90    /// The node is in an invalid state for the requested operation.
91    #[error("node state error: {0}")]
92    StateError(String),
93
94    /// An underlying database error occurred.
95    #[error("database error: {0}")]
96    DatabaseError(String),
97
98    /// A configuration error was detected.
99    #[error("configuration error: {0}")]
100    ConfigError(String),
101
102    /// The node is shutting down and cannot accept new operations.
103    #[error("shutdown in progress")]
104    ShutdownInProgress,
105
106    /// Invalid state transition attempted.
107    #[error("invalid state transition: {0}")]
108    InvalidStateTransition(String),
109
110    /// A node with the same name already exists in the group.
111    #[error("node already exists: {0}")]
112    NodeAlreadyExists(String),
113
114    /// A network channel has been closed.
115    #[error("channel closed: {0}")]
116    ChannelClosed(String),
117
118    /// A requested service was not found.
119    #[error("service not found: {0}")]
120    ServiceNotFound(String),
121
122    /// A subscription error occurred.
123    #[error("subscription error: {0}")]
124    SubscriptionError(String),
125
126    /// A network restore error occurred.
127    #[error("network restore error: {0}")]
128    NetworkRestoreError(String),
129
130    /// The environment has been closed.
131    #[error("environment closed")]
132    EnvironmentClosed,
133
134    /// The node is in an invalid state.
135    #[error("invalid state: {0}")]
136    InvalidState(String),
137}
138
139/// Convenience type alias for replication results.
140pub type Result<T> = std::result::Result<T, RepError>;
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use std::time::Duration;
146
147    #[test]
148    fn test_not_master_with_master() {
149        let err = RepError::NotMaster { master: Some("node1".to_string()) };
150        assert_eq!(
151            err.to_string(),
152            "node is not master: current master is Some(\"node1\")"
153        );
154    }
155
156    #[test]
157    fn test_not_master_without_master() {
158        let err = RepError::NotMaster { master: None };
159        assert_eq!(
160            err.to_string(),
161            "node is not master: current master is None"
162        );
163    }
164
165    #[test]
166    fn test_not_replica() {
167        let err = RepError::NotReplica;
168        assert_eq!(err.to_string(), "node is not replica");
169    }
170
171    #[test]
172    fn test_group_not_found() {
173        let err = RepError::GroupNotFound("mygroup".to_string());
174        assert_eq!(
175            err.to_string(),
176            "replication group does not exist: mygroup"
177        );
178    }
179
180    #[test]
181    fn test_node_not_found() {
182        let err = RepError::NodeNotFound("node2".to_string());
183        assert_eq!(err.to_string(), "node node2 not found in group");
184    }
185
186    #[test]
187    fn test_election_failed() {
188        let err = RepError::ElectionFailed("no quorum".to_string());
189        assert_eq!(err.to_string(), "election failed: no quorum");
190    }
191
192    #[test]
193    fn test_insufficient_acks() {
194        let err = RepError::InsufficientAcks { needed: 3, received: 1 };
195        assert_eq!(err.to_string(), "insufficient acks: needed 3, got 1");
196    }
197
198    #[test]
199    fn test_consistency_timeout() {
200        let err = RepError::ConsistencyTimeout(Duration::from_secs(5));
201        assert_eq!(err.to_string(), "replica consistency timeout after 5s");
202    }
203
204    #[test]
205    fn test_replica_lag_exceeded() {
206        let err = RepError::ReplicaLagExceeded { lag_ms: 5000, limit_ms: 1000 };
207        assert_eq!(
208            err.to_string(),
209            "replica lag too high: 5000ms exceeds limit 1000ms"
210        );
211    }
212
213    #[test]
214    fn test_rollback_required() {
215        let err = RepError::RollbackRequired { from: 100, to: 50 };
216        assert_eq!(err.to_string(), "rollback required: from VLSN 100 to 50");
217    }
218
219    #[test]
220    fn test_network_error() {
221        let err = RepError::NetworkError("connection refused".to_string());
222        assert_eq!(err.to_string(), "network error: connection refused");
223    }
224
225    #[test]
226    fn test_protocol_error() {
227        let err = RepError::ProtocolError("version mismatch".to_string());
228        assert_eq!(err.to_string(), "protocol error: version mismatch");
229    }
230
231    #[test]
232    fn test_state_error() {
233        let err = RepError::StateError("not initialized".to_string());
234        assert_eq!(err.to_string(), "node state error: not initialized");
235    }
236
237    #[test]
238    fn test_database_error() {
239        let err = RepError::DatabaseError("corrupt log".to_string());
240        assert_eq!(err.to_string(), "database error: corrupt log");
241    }
242
243    #[test]
244    fn test_config_error() {
245        let err = RepError::ConfigError("invalid port".to_string());
246        assert_eq!(err.to_string(), "configuration error: invalid port");
247    }
248
249    #[test]
250    fn test_shutdown_in_progress() {
251        let err = RepError::ShutdownInProgress;
252        assert_eq!(err.to_string(), "shutdown in progress");
253    }
254
255    #[test]
256    fn test_result_type_alias() {
257        let ok: Result<u32> = Ok(42);
258        assert!(ok.is_ok_and(|v| v == 42));
259
260        let err: Result<u32> = Err(RepError::NotReplica);
261        assert!(err.is_err());
262    }
263}