wasi_pg_client/reconnect/
classify.rs1use crate::error::PgError;
10use crate::transport::TransportError;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum ErrorClass {
16 Broken,
19
20 Transient,
24
25 Permanent,
29}
30
31pub fn classify_error(err: &PgError) -> ErrorClass {
38 match err {
39 PgError::ConnectionClosed => ErrorClass::Broken,
41 PgError::Transport(TransportError::ConnectionReset) => ErrorClass::Broken,
42 PgError::Transport(TransportError::UnexpectedEof) => ErrorClass::Broken,
43 PgError::Transport(TransportError::ConnectionRefused) => ErrorClass::Broken,
44
45 PgError::Server(ref e) if e.is_serialization_failure() => ErrorClass::Transient,
47 PgError::Server(ref e) if e.is_deadlock_detected() => ErrorClass::Transient,
48 PgError::Server(ref e) if e.is_connection_exception() => ErrorClass::Broken,
50 PgError::Server(ref e) if e.is_admin_shutdown() => ErrorClass::Broken,
51 PgError::Server(ref e) if e.is_crash_shutdown() => ErrorClass::Broken,
52 PgError::Transport(TransportError::Timeout) => ErrorClass::Transient,
53 PgError::Timeout => ErrorClass::Transient,
54
55 PgError::Io(ref e) => match e.kind() {
57 std::io::ErrorKind::ConnectionReset
58 | std::io::ErrorKind::ConnectionAborted
59 | std::io::ErrorKind::BrokenPipe
60 | std::io::ErrorKind::UnexpectedEof => ErrorClass::Broken,
61 std::io::ErrorKind::TimedOut => ErrorClass::Transient,
62 _ => ErrorClass::Permanent,
63 },
64
65 PgError::Server(_) => ErrorClass::Permanent,
67 PgError::TypeConversion(_) => ErrorClass::Permanent,
68 PgError::Config(_) => ErrorClass::Permanent,
69 PgError::Auth(_) => ErrorClass::Permanent,
70
71 _ => ErrorClass::Permanent,
73 }
74}
75
76#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::error::PgServerError;
84 use crate::error::PoolErrorVariant;
85
86 fn make_server_error(code: &str, message: &str) -> PgError {
87 PgError::Server(Box::new(PgServerError::from_fields(vec![
88 (b'S', "ERROR".to_string()),
89 (b'C', code.to_string()),
90 (b'M', message.to_string()),
91 ])))
92 }
93
94 #[test]
95 fn test_classify_broken() {
96 assert_eq!(
97 classify_error(&PgError::ConnectionClosed),
98 ErrorClass::Broken
99 );
100 assert_eq!(
101 classify_error(&PgError::Transport(TransportError::ConnectionReset)),
102 ErrorClass::Broken
103 );
104 assert_eq!(
105 classify_error(&PgError::Transport(TransportError::UnexpectedEof)),
106 ErrorClass::Broken
107 );
108 assert_eq!(
109 classify_error(&PgError::Transport(TransportError::ConnectionRefused)),
110 ErrorClass::Broken
111 );
112
113 let err = make_server_error("08006", "connection failure");
115 assert_eq!(classify_error(&err), ErrorClass::Broken);
116
117 let err = make_server_error("57P01", "admin shutdown");
119 assert_eq!(classify_error(&err), ErrorClass::Broken);
120 }
121
122 #[test]
123 fn test_classify_transient() {
124 let err = make_server_error("40001", "could not serialize access");
126 assert_eq!(classify_error(&err), ErrorClass::Transient);
127
128 let err = make_server_error("40P01", "deadlock detected");
130 assert_eq!(classify_error(&err), ErrorClass::Transient);
131
132 assert_eq!(
134 classify_error(&PgError::Transport(TransportError::Timeout)),
135 ErrorClass::Transient
136 );
137
138 assert_eq!(classify_error(&PgError::Timeout), ErrorClass::Transient);
140 }
141
142 #[test]
143 fn test_classify_permanent() {
144 let err = make_server_error("23505", "duplicate key");
146 assert_eq!(classify_error(&err), ErrorClass::Permanent);
147
148 let err = make_server_error("42601", "syntax error");
150 assert_eq!(classify_error(&err), ErrorClass::Permanent);
151
152 assert_eq!(
154 classify_error(&PgError::Config("bad config".into())),
155 ErrorClass::Permanent
156 );
157
158 assert_eq!(
160 classify_error(&PgError::Auth("bad password".into())),
161 ErrorClass::Permanent
162 );
163
164 assert_eq!(
166 classify_error(&PgError::TypeConversion(crate::types::Error::Conversion(
167 "conversion failed".into(),
168 ))),
169 ErrorClass::Permanent
170 );
171 }
172
173 #[test]
174 fn test_classify_io_errors() {
175 let broken = PgError::Io(std::io::Error::new(
176 std::io::ErrorKind::ConnectionReset,
177 "reset",
178 ));
179 assert_eq!(classify_error(&broken), ErrorClass::Broken);
180
181 let broken = PgError::Io(std::io::Error::new(std::io::ErrorKind::BrokenPipe, "pipe"));
182 assert_eq!(classify_error(&broken), ErrorClass::Broken);
183
184 let transient = PgError::Io(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"));
185 assert_eq!(classify_error(&transient), ErrorClass::Transient);
186
187 let permanent = PgError::Io(std::io::Error::new(
188 std::io::ErrorKind::InvalidInput,
189 "bad input",
190 ));
191 assert_eq!(classify_error(&permanent), ErrorClass::Permanent);
192 }
193
194 #[test]
195 fn test_classify_unknown_errors() {
196 assert_eq!(
198 classify_error(&PgError::Pool(PoolErrorVariant::Exhausted)),
199 ErrorClass::Permanent
200 );
201
202 assert_eq!(
204 classify_error(&PgError::InvalidState("wrong state".into())),
205 ErrorClass::Permanent
206 );
207 }
208}