1use thiserror::Error;
2
3pub type Result<T> = std::result::Result<T, Error>;
4
5pub type Source = Box<dyn std::error::Error + Send + Sync + 'static>;
9
10#[derive(Debug, Error)]
24#[non_exhaustive]
25pub enum Error {
26 #[error("connection failed: {0}")]
27 Connection(String),
28
29 #[error("connection failed: {msg}")]
30 ConnectionWithSource {
31 msg: String,
32 #[source]
33 source: Source,
34 },
35
36 #[error("authentication failed")]
37 Authentication,
38
39 #[error("query failed: {0}")]
40 Query(String),
41
42 #[error("query failed: {msg}")]
43 QueryWithSource {
44 msg: String,
45 #[source]
46 source: Source,
47 },
48
49 #[error("driver `{0}` is not registered")]
50 UnknownDriver(String),
51
52 #[error("unsupported type: {0}")]
53 UnsupportedType(String),
54
55 #[error("feature not supported by this driver: {0}")]
56 Unsupported(String),
57
58 #[error("schema error: {0}")]
59 Schema(String),
60
61 #[error("configuration error: {0}")]
62 Config(String),
63
64 #[error("configuration error: {msg}")]
65 ConfigWithSource {
66 msg: String,
67 #[source]
68 source: Source,
69 },
70
71 #[error("operation was cancelled")]
72 Cancelled,
73
74 #[error("io error: {0}")]
75 Io(#[from] std::io::Error),
76
77 #[error("{0}")]
78 Other(String),
79}
80
81impl Error {
82 pub fn other(msg: impl Into<String>) -> Self {
83 Self::Other(msg.into())
84 }
85
86 pub fn unsupported(msg: impl Into<String>) -> Self {
87 Self::Unsupported(msg.into())
88 }
89
90 pub fn connection_with(msg: impl Into<String>, source: impl Into<Source>) -> Self {
93 Self::ConnectionWithSource {
94 msg: msg.into(),
95 source: source.into(),
96 }
97 }
98
99 pub fn query_with(msg: impl Into<String>, source: impl Into<Source>) -> Self {
102 Self::QueryWithSource {
103 msg: msg.into(),
104 source: source.into(),
105 }
106 }
107
108 pub fn config_with(msg: impl Into<String>, source: impl Into<Source>) -> Self {
111 Self::ConfigWithSource {
112 msg: msg.into(),
113 source: source.into(),
114 }
115 }
116
117 pub fn find_source<T: std::error::Error + 'static>(&self) -> Option<&T> {
121 let mut current: Option<&(dyn std::error::Error + 'static)> =
122 std::error::Error::source(self);
123 while let Some(err) = current {
124 if let Some(target) = err.downcast_ref::<T>() {
125 return Some(target);
126 }
127 current = err.source();
128 }
129 None
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[derive(Debug, Error)]
138 #[error("driver-specific boom: code={0}")]
139 struct FakeDriverError(u32);
140
141 #[test]
142 fn connection_with_preserves_source() {
143 let driver = FakeDriverError(42);
144 let err = Error::connection_with("failed to handshake", driver);
145 assert!(matches!(err, Error::ConnectionWithSource { .. }));
146 assert_eq!(err.to_string(), "connection failed: failed to handshake");
147 let found = err.find_source::<FakeDriverError>().expect("chain");
148 assert_eq!(found.0, 42);
149 }
150
151 #[test]
152 fn query_with_chain_is_walkable() {
153 let err = Error::query_with("select bombed", FakeDriverError(7));
154 let mut chain: Option<&(dyn std::error::Error + 'static)> = std::error::Error::source(&err);
155 let mut hops = 0;
156 while let Some(c) = chain {
157 hops += 1;
158 chain = c.source();
159 }
160 assert!(hops >= 1);
161 }
162
163 #[test]
164 fn legacy_string_variants_unchanged() {
165 let err = Error::Connection("plain".into());
168 assert_eq!(err.to_string(), "connection failed: plain");
169 assert!(std::error::Error::source(&err).is_none());
170 }
171}