Skip to main content

br_db/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum DbError {
5    #[error("Configuration error: {0}")]
6    Config(String),
7
8    #[error("Connection error: {0}")]
9    Connection(String),
10
11    #[error("Query error: {0}")]
12    Query(String),
13
14    #[error("Transaction error: {0}")]
15    Transaction(String),
16
17    #[error("Lock error: {0}")]
18    Lock(String),
19
20    #[error("SQL injection detected: {0}")]
21    SqlInjection(String),
22
23    #[error("Invalid table name: {0}")]
24    InvalidTableName(String),
25
26    #[error("Invalid field name: {0}")]
27    InvalidFieldName(String),
28
29    #[error("IO error: {0}")]
30    Io(#[from] std::io::Error),
31
32    #[error("Parse error: {0}")]
33    Parse(String),
34
35    #[error("Pool error: {0}")]
36    Pool(String),
37
38    #[error("Timeout error: {0}")]
39    Timeout(String),
40
41    #[error("Not found: {0}")]
42    NotFound(String),
43
44    #[error("Database not initialized")]
45    NotInitialized,
46
47    #[error("Unknown error: {0}")]
48    Unknown(String),
49}
50
51pub type DbResult<T> = Result<T, DbError>;
52
53impl From<String> for DbError {
54    fn from(s: String) -> Self {
55        DbError::Unknown(s)
56    }
57}
58
59impl From<&str> for DbError {
60    fn from(s: &str) -> Self {
61        DbError::Unknown(s.to_string())
62    }
63}
64
65impl<T> From<std::sync::PoisonError<T>> for DbError {
66    fn from(e: std::sync::PoisonError<T>) -> Self {
67        DbError::Lock(e.to_string())
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use std::error::Error;
75
76    use std::sync::{Arc, Mutex};
77
78    #[test]
79    fn display_messages_for_all_variants() {
80        assert_eq!(
81            DbError::Config("bad cfg".to_string()).to_string(),
82            "Configuration error: bad cfg"
83        );
84        assert_eq!(
85            DbError::Connection("conn down".to_string()).to_string(),
86            "Connection error: conn down"
87        );
88        assert_eq!(
89            DbError::Query("bad sql".to_string()).to_string(),
90            "Query error: bad sql"
91        );
92        assert_eq!(
93            DbError::Transaction("rollback".to_string()).to_string(),
94            "Transaction error: rollback"
95        );
96        assert_eq!(
97            DbError::Lock("mutex".to_string()).to_string(),
98            "Lock error: mutex"
99        );
100        assert_eq!(
101            DbError::SqlInjection("DROP TABLE".to_string()).to_string(),
102            "SQL injection detected: DROP TABLE"
103        );
104        assert_eq!(
105            DbError::InvalidTableName("bad table".to_string()).to_string(),
106            "Invalid table name: bad table"
107        );
108        assert_eq!(
109            DbError::InvalidFieldName("bad field".to_string()).to_string(),
110            "Invalid field name: bad field"
111        );
112
113        let io_err = std::io::Error::other("disk fail");
114        assert_eq!(DbError::Io(io_err).to_string(), "IO error: disk fail");
115
116        assert_eq!(
117            DbError::Parse("invalid json".to_string()).to_string(),
118            "Parse error: invalid json"
119        );
120        assert_eq!(
121            DbError::Pool("pool exhausted".to_string()).to_string(),
122            "Pool error: pool exhausted"
123        );
124        assert_eq!(
125            DbError::Timeout("30s".to_string()).to_string(),
126            "Timeout error: 30s"
127        );
128        assert_eq!(
129            DbError::NotFound("record".to_string()).to_string(),
130            "Not found: record"
131        );
132        assert_eq!(
133            DbError::NotInitialized.to_string(),
134            "Database not initialized"
135        );
136        assert_eq!(
137            DbError::Unknown("other".to_string()).to_string(),
138            "Unknown error: other"
139        );
140    }
141
142    #[test]
143    fn from_string_maps_to_unknown() {
144        let err: DbError = String::from("string failure").into();
145        match err {
146            DbError::Unknown(msg) => assert_eq!(msg, "string failure"),
147            _ => panic!("expected DbError::Unknown"),
148        }
149    }
150
151    #[test]
152    fn from_str_maps_to_unknown() {
153        let err: DbError = "str failure".into();
154        match err {
155            DbError::Unknown(msg) => assert_eq!(msg, "str failure"),
156            _ => panic!("expected DbError::Unknown"),
157        }
158    }
159
160    #[test]
161    fn from_poison_error_maps_to_lock() {
162        let mutex = Arc::new(Mutex::new(1));
163        let mutex_in_thread = Arc::clone(&mutex);
164
165        let handle = std::thread::spawn(move || {
166            let _guard = mutex_in_thread.lock().expect("lock in worker");
167            panic!("poison mutex");
168        });
169        let _ = handle.join();
170
171        let poison_err = mutex.lock().expect_err("lock should be poisoned");
172        let err: DbError = poison_err.into();
173        match err {
174            DbError::Lock(msg) => assert!(
175                msg.contains("poison"),
176                "unexpected lock error message: {msg}"
177            ),
178            _ => panic!("expected DbError::Lock"),
179        }
180    }
181
182    #[test]
183    fn dbresult_alias_ok_and_err() {
184        assert_eq!(7_i32, 7);
185
186        let err = DbError::NotFound("id=1".to_string());
187        assert_eq!(err.to_string(), "Not found: id=1");
188    }
189
190    #[test]
191    fn error_trait_behavior() {
192        fn assert_is_error<E: Error>(_err: &E) {}
193
194        let query_err = DbError::Query("bad query".to_string());
195        assert_is_error(&query_err);
196        assert_eq!(query_err.to_string(), "Query error: bad query");
197        assert!(query_err.source().is_none());
198
199        let io_err = DbError::Io(std::io::Error::other("io root cause"));
200        assert_eq!(io_err.to_string(), "IO error: io root cause");
201        assert_eq!(
202            io_err.source().expect("io source").to_string(),
203            "io root cause"
204        );
205    }
206}