Skip to main content

gemini_live/
error.rs

1//! Error types for each architectural layer.
2//!
3//! Errors are split by layer so callers can match on the granularity they need:
4//! - [`CodecError`] — JSON serialization / deserialization failures.
5//! - [`ConnectError`] — WebSocket connection establishment failures.
6//! - [`SendError`] / [`RecvError`] — frame-level I/O on an established connection.
7//! - [`SessionError`] — high-level session lifecycle errors (setup, reconnect, etc.).
8
9use std::time::Duration;
10
11// ── Codec layer ──────────────────────────────────────────────────────────────
12
13/// Failure during JSON ↔ Rust conversion.
14#[derive(Debug, thiserror::Error)]
15pub enum CodecError {
16    #[error("JSON serialization failed: {0}")]
17    Serialize(#[source] serde_json::Error),
18    #[error("JSON deserialization failed: {0}")]
19    Deserialize(#[source] serde_json::Error),
20}
21
22// ── Auth helper errors ───────────────────────────────────────────────────────
23
24/// Failure while obtaining an OAuth bearer token for a WebSocket handshake.
25#[derive(Debug, thiserror::Error)]
26#[error("{message}")]
27pub struct BearerTokenError {
28    message: String,
29    #[source]
30    source: Option<Box<dyn std::error::Error + Send + Sync>>,
31}
32
33impl BearerTokenError {
34    /// Create a token error without an underlying source.
35    pub fn new(message: impl Into<String>) -> Self {
36        Self {
37            message: message.into(),
38            source: None,
39        }
40    }
41
42    /// Create a token error with an underlying source error.
43    pub fn with_source(
44        message: impl Into<String>,
45        source: impl std::error::Error + Send + Sync + 'static,
46    ) -> Self {
47        Self {
48            message: message.into(),
49            source: Some(Box::new(source)),
50        }
51    }
52}
53
54// ── Transport layer ──────────────────────────────────────────────────────────
55
56/// Failure while establishing a WebSocket connection.
57#[derive(Debug, thiserror::Error)]
58pub enum ConnectError {
59    #[error("invalid transport configuration: {0}")]
60    Config(String),
61    #[error("failed to obtain bearer token: {0}")]
62    Auth(#[source] BearerTokenError),
63    #[error("DNS resolution failed: {0}")]
64    Dns(#[source] Box<dyn std::error::Error + Send + Sync>),
65    #[error("TLS handshake failed: {0}")]
66    Tls(#[source] Box<dyn std::error::Error + Send + Sync>),
67    #[error("connection timed out after {0:?}")]
68    Timeout(Duration),
69    #[error("WebSocket handshake rejected: {status}")]
70    Rejected { status: u16 },
71    #[error("WebSocket error: {0}")]
72    Ws(#[source] tokio_tungstenite::tungstenite::Error),
73}
74
75/// Failure while sending a WebSocket frame.
76#[derive(Debug, thiserror::Error)]
77pub enum SendError {
78    #[error("connection closed")]
79    Closed,
80    #[error("WebSocket send failed: {0}")]
81    Ws(#[source] tokio_tungstenite::tungstenite::Error),
82}
83
84/// Failure while receiving a WebSocket frame.
85#[derive(Debug, thiserror::Error)]
86pub enum RecvError {
87    #[error("connection closed")]
88    Closed,
89    #[error("WebSocket receive failed: {0}")]
90    Ws(#[source] tokio_tungstenite::tungstenite::Error),
91}
92
93// ── Session layer ────────────────────────────────────────────────────────────
94
95/// High-level session error covering setup, runtime, and reconnection.
96#[derive(Debug, thiserror::Error)]
97pub enum SessionError {
98    #[error("setup failed: {0}")]
99    SetupFailed(String),
100    #[error("setup timed out after {0:?}")]
101    SetupTimeout(Duration),
102    #[error("API error: {0}")]
103    Api(String),
104    #[error("connection lost and reconnection failed after {attempts} attempts")]
105    ReconnectExhausted { attempts: u32 },
106    #[error("session closed")]
107    Closed,
108    #[error(transparent)]
109    Transport(#[from] SendError),
110    #[error(transparent)]
111    Codec(#[from] CodecError),
112}