1#[derive(Debug, thiserror::Error)]
2pub enum OversyncError {
3 #[error("surrealdb: {0}")]
4 SurrealDb(String),
5
6 #[error("connector: {0}")]
7 Connector(String),
8
9 #[error("sink: {0}")]
10 Sink(String),
11
12 #[error("config: {0}")]
13 Config(String),
14
15 #[error("migration: {0}")]
16 Migration(String),
17
18 #[error("plugin: {0}")]
19 Plugin(String),
20
21 #[error("fail-safe: {deleted_count}/{previous_count} rows deleted (>{threshold_pct:.0}%)")]
22 FailSafe {
23 deleted_count: usize,
24 previous_count: usize,
25 threshold_pct: f64,
26 },
27
28 #[error("internal: {0}")]
29 Internal(String),
30}
31
32impl OversyncError {
33 pub fn is_fail_safe(&self) -> bool {
34 matches!(self, Self::FailSafe { .. })
35 }
36}
37
38impl From<serde_json::Error> for OversyncError {
39 fn from(e: serde_json::Error) -> Self {
40 Self::Internal(e.to_string())
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn display_all_variants() {
50 let cases: Vec<(OversyncError, &str)> = vec![
51 (OversyncError::SurrealDb("conn".into()), "surrealdb: conn"),
52 (
53 OversyncError::Connector("timeout".into()),
54 "connector: timeout",
55 ),
56 (OversyncError::Sink("full".into()), "sink: full"),
57 (OversyncError::Config("bad".into()), "config: bad"),
58 (OversyncError::Migration("v1".into()), "migration: v1"),
59 (OversyncError::Plugin("missing".into()), "plugin: missing"),
60 (
61 OversyncError::FailSafe {
62 deleted_count: 8,
63 previous_count: 10,
64 threshold_pct: 30.0,
65 },
66 "fail-safe: 8/10 rows deleted (>30%)",
67 ),
68 (OversyncError::Internal("oops".into()), "internal: oops"),
69 ];
70 for (err, expected) in cases {
71 assert_eq!(err.to_string(), expected);
72 }
73 }
74
75 #[test]
76 fn serde_json_error_converts() {
77 let bad_json = serde_json::from_str::<serde_json::Value>("not json");
78 let oversync_err: OversyncError = bad_json.unwrap_err().into();
79 assert!(matches!(oversync_err, OversyncError::Internal(_)));
80 }
81
82 #[test]
83 fn fail_safe_helper_detects_variant() {
84 let err = OversyncError::FailSafe {
85 deleted_count: 4,
86 previous_count: 5,
87 threshold_pct: 30.0,
88 };
89 assert!(err.is_fail_safe());
90 assert!(!OversyncError::Internal("oops".into()).is_fail_safe());
91 }
92}