nntp_proxy/session/
error_classification.rs1use crate::connection_error::ConnectionError;
6use std::io::ErrorKind;
7
8pub struct ErrorClassifier;
10
11impl ErrorClassifier {
12 pub fn is_client_disconnect(error: &anyhow::Error) -> bool {
14 if let Some(conn_err) = error.downcast_ref::<ConnectionError>() {
16 return conn_err.is_client_disconnect();
17 }
18
19 if let Some(io_err) = error.downcast_ref::<std::io::Error>() {
21 return io_err.kind() == ErrorKind::BrokenPipe;
22 }
23
24 false
25 }
26
27 pub fn is_authentication_error(error: &anyhow::Error) -> bool {
29 if let Some(conn_err) = error.downcast_ref::<ConnectionError>() {
31 return conn_err.is_authentication_error();
32 }
33
34 let error_str = error.to_string();
36 error_str.contains("Auth failed") || error_str.contains("Authentication Failed")
37 }
38
39 pub fn is_network_error(error: &anyhow::Error) -> bool {
41 if let Some(conn_err) = error.downcast_ref::<ConnectionError>() {
42 return conn_err.is_network_error();
43 }
44 false
45 }
46
47 pub fn should_skip_client_error_response(error: &anyhow::Error) -> bool {
49 Self::is_client_disconnect(error)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn test_is_client_disconnect_with_io_error() {
59 let io_err = std::io::Error::new(ErrorKind::BrokenPipe, "broken pipe");
60 let err: anyhow::Error = io_err.into();
61 assert!(ErrorClassifier::is_client_disconnect(&err));
62 }
63
64 #[test]
65 fn test_is_client_disconnect_with_connection_error() {
66 let io_err = std::io::Error::new(ErrorKind::BrokenPipe, "broken pipe");
67 let conn_err = ConnectionError::IoError(io_err);
68 let err: anyhow::Error = conn_err.into();
69 assert!(ErrorClassifier::is_client_disconnect(&err));
70 }
71
72 #[test]
73 fn test_is_authentication_error_with_connection_error() {
74 let conn_err = ConnectionError::AuthenticationFailed {
75 backend: "test".to_string(),
76 response: "502 failed".to_string(),
77 };
78 let err: anyhow::Error = conn_err.into();
79 assert!(ErrorClassifier::is_authentication_error(&err));
80 }
81
82 #[test]
83 fn test_is_authentication_error_with_message() {
84 let err = anyhow::anyhow!("Auth failed: invalid credentials");
85 assert!(ErrorClassifier::is_authentication_error(&err));
86 }
87
88 #[test]
89 fn test_should_skip_client_error_response() {
90 let io_err = std::io::Error::new(ErrorKind::BrokenPipe, "broken");
91 let err: anyhow::Error = io_err.into();
92 assert!(ErrorClassifier::should_skip_client_error_response(&err));
93
94 let other_err = anyhow::anyhow!("some other error");
95 assert!(!ErrorClassifier::should_skip_client_error_response(
96 &other_err
97 ));
98 }
99
100 #[test]
101 fn test_client_disconnect_classification() {
102 let broken_pipe = std::io::Error::new(ErrorKind::BrokenPipe, "pipe");
104 let err: anyhow::Error = broken_pipe.into();
105 assert!(ErrorClassifier::is_client_disconnect(&err));
106 assert!(ErrorClassifier::should_skip_client_error_response(&err));
107
108 let reset = std::io::Error::new(ErrorKind::ConnectionReset, "reset");
110 let err: anyhow::Error = reset.into();
111 assert!(!ErrorClassifier::is_client_disconnect(&err));
112 assert!(!ErrorClassifier::should_skip_client_error_response(&err));
113 }
114
115 #[test]
116 fn test_error_classification_layering() {
117 let broken_pipe = std::io::Error::new(ErrorKind::BrokenPipe, "pipe");
122 let err: anyhow::Error = broken_pipe.into();
123 assert!(ErrorClassifier::is_client_disconnect(&err));
124 assert!(!ErrorClassifier::is_authentication_error(&err));
125 assert!(!ErrorClassifier::is_network_error(&err));
126
127 let auth_fail = ConnectionError::AuthenticationFailed {
129 backend: "test".to_string(),
130 response: "nope".to_string(),
131 };
132 let err: anyhow::Error = auth_fail.into();
133 assert!(!ErrorClassifier::is_client_disconnect(&err));
134 assert!(ErrorClassifier::is_authentication_error(&err));
135 assert!(!ErrorClassifier::is_network_error(&err));
136
137 let net_err = ConnectionError::TcpConnect {
139 host: "test".to_string(),
140 port: 119,
141 source: std::io::Error::new(ErrorKind::ConnectionRefused, "refused"),
142 };
143 let err: anyhow::Error = net_err.into();
144 assert!(!ErrorClassifier::is_client_disconnect(&err));
145 assert!(!ErrorClassifier::is_authentication_error(&err));
146 assert!(ErrorClassifier::is_network_error(&err));
147 }
148}