rs_genai/session/
errors.rs1use super::state::SessionPhase;
7use thiserror::Error;
8
9#[derive(Debug, Error, Clone)]
11pub enum SessionError {
12 #[error("WebSocket error: {0}")]
14 WebSocket(WebSocketError),
15
16 #[error("Timeout in {phase} after {elapsed:?}")]
18 Timeout {
19 phase: SessionPhase,
21 elapsed: std::time::Duration,
23 },
24
25 #[error("Invalid transition from {from} to {to}")]
27 InvalidTransition {
28 from: SessionPhase,
30 to: SessionPhase,
32 },
33
34 #[error("Not connected")]
36 NotConnected,
37
38 #[error("Setup failed: {0}")]
40 SetupFailed(SetupError),
41
42 #[error("Server sent GoAway (time left: {time_left:?})")]
44 GoAway {
45 time_left: Option<std::time::Duration>,
47 },
48
49 #[error("Internal channel closed")]
51 ChannelClosed,
52
53 #[error("Send queue full")]
55 SendQueueFull,
56
57 #[error("Auth error: {0}")]
59 Auth(AuthError),
60}
61
62#[derive(Debug, Error, Clone)]
64pub enum WebSocketError {
65 #[error("Connection refused: {0}")]
67 ConnectionRefused(String),
68
69 #[error("Protocol error: {0}")]
71 ProtocolError(String),
72
73 #[error("Connection closed (code={code}, reason={reason})")]
75 Closed {
76 code: u16,
78 reason: String,
80 },
81}
82
83#[derive(Debug, Error, Clone)]
85pub enum SetupError {
86 #[error("Invalid model: {0}")]
88 InvalidModel(String),
89
90 #[error("Authentication failed: {0}")]
92 AuthenticationFailed(String),
93
94 #[error("Server rejected: {message}")]
96 ServerRejected {
97 code: Option<String>,
99 message: String,
101 },
102
103 #[error("Setup timed out")]
105 Timeout,
106}
107
108#[derive(Debug, Error, Clone)]
110pub enum AuthError {
111 #[error("Token expired")]
113 TokenExpired,
114
115 #[error("Token fetch failed: {0}")]
117 TokenFetchFailed(String),
118
119 #[error("Insufficient scopes: {0}")]
121 InsufficientScopes(String),
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::time::Duration;
128
129 #[test]
134 fn websocket_error_connection_refused_display() {
135 let err = WebSocketError::ConnectionRefused("host unreachable".into());
136 assert_eq!(err.to_string(), "Connection refused: host unreachable");
137 }
138
139 #[test]
140 fn websocket_error_protocol_error_display() {
141 let err = WebSocketError::ProtocolError("invalid frame".into());
142 assert_eq!(err.to_string(), "Protocol error: invalid frame");
143 }
144
145 #[test]
146 fn websocket_error_closed_display() {
147 let err = WebSocketError::Closed {
148 code: 1006,
149 reason: "abnormal closure".into(),
150 };
151 assert_eq!(
152 err.to_string(),
153 "Connection closed (code=1006, reason=abnormal closure)"
154 );
155 }
156
157 #[test]
162 fn setup_error_invalid_model_display() {
163 let err = SetupError::InvalidModel("no-such-model".into());
164 assert_eq!(err.to_string(), "Invalid model: no-such-model");
165 }
166
167 #[test]
168 fn setup_error_authentication_failed_display() {
169 let err = SetupError::AuthenticationFailed("bad token".into());
170 assert_eq!(err.to_string(), "Authentication failed: bad token");
171 }
172
173 #[test]
174 fn setup_error_server_rejected_display() {
175 let err = SetupError::ServerRejected {
176 code: Some("400".into()),
177 message: "invalid config".into(),
178 };
179 assert_eq!(err.to_string(), "Server rejected: invalid config");
180 }
181
182 #[test]
183 fn setup_error_server_rejected_no_code_display() {
184 let err = SetupError::ServerRejected {
185 code: None,
186 message: "closed during setup".into(),
187 };
188 assert_eq!(err.to_string(), "Server rejected: closed during setup");
189 }
190
191 #[test]
192 fn setup_error_timeout_display() {
193 let err = SetupError::Timeout;
194 assert_eq!(err.to_string(), "Setup timed out");
195 }
196
197 #[test]
202 fn auth_error_token_expired_display() {
203 let err = AuthError::TokenExpired;
204 assert_eq!(err.to_string(), "Token expired");
205 }
206
207 #[test]
208 fn auth_error_token_fetch_failed_display() {
209 let err = AuthError::TokenFetchFailed("network error".into());
210 assert_eq!(err.to_string(), "Token fetch failed: network error");
211 }
212
213 #[test]
214 fn auth_error_insufficient_scopes_display() {
215 let err = AuthError::InsufficientScopes("cloud-platform".into());
216 assert_eq!(err.to_string(), "Insufficient scopes: cloud-platform");
217 }
218
219 #[test]
224 fn session_error_websocket_display() {
225 let err =
226 SessionError::WebSocket(WebSocketError::ConnectionRefused("host unreachable".into()));
227 assert_eq!(
228 err.to_string(),
229 "WebSocket error: Connection refused: host unreachable"
230 );
231 }
232
233 #[test]
234 fn session_error_timeout_display() {
235 let err = SessionError::Timeout {
236 phase: SessionPhase::SetupSent,
237 elapsed: Duration::from_secs(15),
238 };
239 assert_eq!(err.to_string(), "Timeout in SetupSent after 15s");
240 }
241
242 #[test]
243 fn session_error_timeout_connecting_display() {
244 let err = SessionError::Timeout {
245 phase: SessionPhase::Connecting,
246 elapsed: Duration::from_secs(10),
247 };
248 assert_eq!(err.to_string(), "Timeout in Connecting after 10s");
249 }
250
251 #[test]
252 fn session_error_setup_failed_display() {
253 let err = SessionError::SetupFailed(SetupError::AuthenticationFailed("bad token".into()));
254 assert_eq!(
255 err.to_string(),
256 "Setup failed: Authentication failed: bad token"
257 );
258 }
259
260 #[test]
261 fn session_error_go_away_with_time_display() {
262 let err = SessionError::GoAway {
263 time_left: Some(Duration::from_secs(30)),
264 };
265 assert_eq!(err.to_string(), "Server sent GoAway (time left: Some(30s))");
266 }
267
268 #[test]
269 fn session_error_go_away_no_time_display() {
270 let err = SessionError::GoAway { time_left: None };
271 assert_eq!(err.to_string(), "Server sent GoAway (time left: None)");
272 }
273
274 #[test]
275 fn session_error_auth_display() {
276 let err = SessionError::Auth(AuthError::TokenExpired);
277 assert_eq!(err.to_string(), "Auth error: Token expired");
278 }
279
280 #[test]
281 fn session_error_not_connected_display() {
282 let err = SessionError::NotConnected;
283 assert_eq!(err.to_string(), "Not connected");
284 }
285
286 #[test]
287 fn session_error_channel_closed_display() {
288 let err = SessionError::ChannelClosed;
289 assert_eq!(err.to_string(), "Internal channel closed");
290 }
291
292 #[test]
293 fn session_error_send_queue_full_display() {
294 let err = SessionError::SendQueueFull;
295 assert_eq!(err.to_string(), "Send queue full");
296 }
297
298 #[test]
299 fn session_error_invalid_transition_display() {
300 let err = SessionError::InvalidTransition {
301 from: SessionPhase::Active,
302 to: SessionPhase::SetupSent,
303 };
304 assert_eq!(
305 err.to_string(),
306 "Invalid transition from Active to SetupSent"
307 );
308 }
309
310 #[test]
315 fn error_types_are_clone() {
316 let ws_err = WebSocketError::ProtocolError("test".into());
317 let _ = ws_err.clone();
318
319 let setup_err = SetupError::InvalidModel("test".into());
320 let _ = setup_err.clone();
321
322 let auth_err = AuthError::TokenExpired;
323 let _ = auth_err.clone();
324
325 let session_err = SessionError::WebSocket(WebSocketError::ProtocolError("test".into()));
326 let _ = session_err.clone();
327 }
328}