Skip to main content

ig_client/
error.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 12/5/25
5******************************************************************************/
6use reqwest::StatusCode;
7use std::io;
8
9/// Error type for fetch operations
10#[derive(Debug, thiserror::Error)]
11pub enum FetchError {
12    /// Network error from reqwest
13    #[error("network error: {0}")]
14    Reqwest(#[from] reqwest::Error),
15    /// Database error from sqlx
16    #[error("db error: {0}")]
17    Sqlx(#[from] sqlx::Error),
18    /// Error during parsing
19    #[error("parser error: {0}")]
20    Parser(String),
21}
22
23/// Error type for authentication operations
24#[derive(Debug, thiserror::Error)]
25pub enum AuthError {
26    /// Network error from reqwest
27    #[error("network error: {0}")]
28    Network(#[from] reqwest::Error),
29    /// I/O error
30    #[error("io error: {0}")]
31    Io(#[from] io::Error),
32    /// JSON serialization or deserialization error
33    #[error("json error: {0}")]
34    Json(#[from] serde_json::Error),
35    /// Other unspecified error
36    #[error("other error: {0}")]
37    Other(String),
38    /// Invalid credentials error
39    #[error("bad credentials")]
40    BadCredentials,
41    /// Unexpected HTTP status code
42    #[error("unexpected http status: {0}")]
43    Unexpected(StatusCode),
44    /// Rate limit exceeded error
45    #[error("rate limit exceeded")]
46    RateLimitExceeded,
47}
48
49impl From<Box<dyn std::error::Error + Send + Sync>> for AuthError {
50    #[cold]
51    fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
52        match e.downcast::<reqwest::Error>() {
53            Ok(req) => AuthError::Network(*req),
54            Err(e) => match e.downcast::<serde_json::Error>() {
55                Ok(js) => AuthError::Json(*js),
56                Err(e) => match e.downcast::<std::io::Error>() {
57                    Ok(ioe) => AuthError::Io(*ioe),
58                    Err(other) => AuthError::Other(other.to_string()),
59                },
60            },
61        }
62    }
63}
64
65impl From<Box<dyn std::error::Error>> for AuthError {
66    #[cold]
67    fn from(e: Box<dyn std::error::Error>) -> Self {
68        match e.downcast::<reqwest::Error>() {
69            Ok(req) => AuthError::Network(*req),
70            Err(e) => match e.downcast::<serde_json::Error>() {
71                Ok(js) => AuthError::Json(*js),
72                Err(e) => match e.downcast::<io::Error>() {
73                    Ok(ioe) => AuthError::Io(*ioe),
74                    Err(other) => AuthError::Other(other.to_string()),
75                },
76            },
77        }
78    }
79}
80
81impl From<AppError> for AuthError {
82    #[cold]
83    fn from(e: AppError) -> Self {
84        match e {
85            AppError::Network(e) => AuthError::Network(e),
86            AppError::Io(e) => AuthError::Io(e),
87            AppError::Json(e) => AuthError::Json(e),
88            AppError::Unexpected(s) => AuthError::Unexpected(s),
89            _ => AuthError::Other(e.to_string()),
90        }
91    }
92}
93
94/// General application error type
95#[derive(Debug, thiserror::Error)]
96pub enum AppError {
97    /// Network error from reqwest
98    #[error("network error: {0}")]
99    Network(#[from] reqwest::Error),
100    /// I/O error
101    #[error("io error: {0}")]
102    Io(#[from] io::Error),
103    /// JSON serialization or deserialization error
104    #[error("json error: {0}")]
105    Json(#[from] serde_json::Error),
106    /// Unexpected HTTP status code
107    #[error("unexpected http status: {0}")]
108    Unexpected(StatusCode),
109    /// Database error from sqlx
110    #[error("db error: {0}")]
111    Db(#[from] sqlx::Error),
112    /// Unauthorized access error
113    #[error("unauthorized")]
114    Unauthorized,
115    /// OAuth token expired error (requires token refresh)
116    #[error("oauth token expired")]
117    OAuthTokenExpired,
118    /// Resource not found error
119    #[error("not found")]
120    NotFound,
121    /// API rate limit exceeded
122    #[error("rate limit exceeded")]
123    RateLimitExceeded,
124    /// Historical data allowance exhausted (weekly quota of data points)
125    ///
126    /// The `allowance_expiry` field indicates the number of seconds
127    /// until the allowance resets. Retrying before that is pointless.
128    #[error("historical data allowance exceeded, resets in {allowance_expiry} seconds")]
129    HistoricalDataAllowanceExceeded {
130        /// Seconds until the weekly allowance resets
131        allowance_expiry: u64,
132    },
133    /// Error during serialization or deserialization
134    #[error("serialization error: {0}")]
135    SerializationError(String),
136    /// WebSocket communication error
137    #[error("websocket error: {0}")]
138    WebSocketError(String),
139    /// Deserialization error with details
140    #[error("deserialization error: {0}")]
141    Deserialization(String),
142    /// Invalid input error with a description of the constraint violated
143    #[error("invalid input: {0}")]
144    InvalidInput(String),
145    /// Generic error for cases that don't fit other categories
146    #[error("generic error: {0}")]
147    Generic(String),
148}
149
150impl From<AuthError> for AppError {
151    #[cold]
152    fn from(e: AuthError) -> Self {
153        match e {
154            AuthError::Network(e) => AppError::Network(e),
155            AuthError::Io(e) => AppError::Io(e),
156            AuthError::Json(e) => AppError::Json(e),
157            AuthError::BadCredentials => AppError::Unauthorized,
158            AuthError::Unexpected(s) => AppError::Unexpected(s),
159            _ => AppError::Unexpected(StatusCode::INTERNAL_SERVER_ERROR),
160        }
161    }
162}
163
164impl From<Box<dyn std::error::Error>> for AppError {
165    #[cold]
166    fn from(e: Box<dyn std::error::Error>) -> Self {
167        match e.downcast::<reqwest::Error>() {
168            Ok(req) => AppError::Network(*req),
169            Err(e) => match e.downcast::<serde_json::Error>() {
170                Ok(js) => AppError::Json(*js),
171                Err(e) => match e.downcast::<std::io::Error>() {
172                    Ok(ioe) => AppError::Io(*ioe),
173                    Err(_) => AppError::Unexpected(StatusCode::INTERNAL_SERVER_ERROR),
174                },
175            },
176        }
177    }
178}
179
180impl From<String> for AppError {
181    #[cold]
182    fn from(e: String) -> Self {
183        AppError::Generic(e)
184    }
185}
186
187impl From<lightstreamer_rs::utils::LightstreamerError> for AppError {
188    #[cold]
189    fn from(e: lightstreamer_rs::utils::LightstreamerError) -> Self {
190        AppError::WebSocketError(e.to_string())
191    }
192}