Skip to main content

sockudo_core/
error.rs

1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum Error {
5    // 4000-4099: Don't reconnect errors
6    #[error("Application only accepts SSL connections, reconnect using wss://")]
7    SSLRequired,
8
9    #[error("Application does not exist")]
10    ApplicationNotFound,
11
12    #[error("Application disabled")]
13    ApplicationDisabled,
14
15    #[error("Application is over adapter quota")]
16    OverConnectionQuota,
17
18    #[error("Path not found")]
19    PathNotFound,
20
21    #[error("Invalid version string format")]
22    InvalidVersionFormat,
23
24    #[error("Unsupported protocol version: {0}")]
25    UnsupportedProtocolVersion(String),
26
27    #[error("No protocol version supplied")]
28    NoProtocolVersion,
29
30    #[error("Connection is unauthorized")]
31    Unauthorized,
32
33    #[error("Origin not allowed")]
34    OriginNotAllowed,
35
36    // 4100-4199: Reconnect with backoff errors
37    #[error("Over capacity")]
38    OverCapacity,
39
40    // 4200-4299: Reconnect immediately errors
41    #[error("Generic reconnect immediately")]
42    ReconnectImmediately,
43
44    #[error("Pong reply not received")]
45    PongNotReceived,
46
47    #[error("Closed after inactivity")]
48    InactivityTimeout,
49
50    // 4300-4399: Other errors
51    #[error("Client event rejected due to rate limit")]
52    ClientEventRateLimit,
53
54    #[error("Connection closed due to client event rate limit")]
55    ClientEventRateLimitTerminate,
56
57    #[error("Watchlist limit exceeded")]
58    WatchlistLimitExceeded,
59
60    // Channel specific errors
61    #[error("Channel error: {0}")]
62    Channel(String),
63
64    #[error("Channel name invalid: {0}")]
65    InvalidChannelName(String),
66
67    #[error("Channel already exists")]
68    ChannelExists,
69
70    #[error("Channel does not exist")]
71    ChannelNotFound,
72
73    // Authentication errors
74    #[error("Authentication error: {0}")]
75    Auth(String),
76
77    #[error("Invalid signature")]
78    InvalidSignature,
79
80    #[error("Invalid key")]
81    InvalidKey,
82
83    // Connection errors
84    #[error("Connection error: {0}")]
85    Connection(String),
86
87    #[error("Connection already exists")]
88    ConnectionExists,
89
90    #[error("Connection not found")]
91    ConnectionNotFound,
92
93    #[error("Connection closed: {0}")]
94    ConnectionClosed(String),
95
96    #[error("Buffer full: {0}")]
97    BufferFull(String),
98
99    // Protocol errors
100    #[error("Protocol error: {0}")]
101    Protocol(String),
102
103    #[error("Invalid message format: {0}")]
104    InvalidMessageFormat(String),
105
106    #[error("Invalid event name: {0}")]
107    InvalidEventName(String),
108
109    // WebSocket errors
110    #[error("WebSocket error: {0}")]
111    WebSocket(#[from] sockudo_ws::Error),
112
113    // Internal errors
114    #[error("Internal server error: {0}")]
115    Internal(String),
116
117    // JSON serialization/deserialization errors
118    #[error("JSON error: {0}")]
119    Json(#[from] sonic_rs::Error),
120
121    #[error("Client event error: {0}")]
122    ClientEvent(String),
123
124    // I/O errors
125    #[error("I/O error: {0}")]
126    Io(#[from] std::io::Error),
127
128    // Generic errors
129    #[error("Invalid app key")]
130    InvalidAppKey,
131
132    #[error("Cache error: {0}")]
133    Cache(String),
134
135    #[error("Invalid JSON")]
136    Serialization(String),
137
138    #[error("Broadcast error: {0}")]
139    Broadcast(String),
140
141    #[error("Other: {0}")]
142    Other(String),
143
144    #[error("Redis error: {0}")]
145    Redis(String),
146
147    #[error("Request timeout")]
148    RequestTimeout,
149
150    #[error("Own request ignored")]
151    OwnRequestIgnored,
152
153    #[error("Request not for this node")]
154    RequestNotForThisNode,
155
156    #[error("Horizontal adapter error: {0}")]
157    HorizontalAdapter(String),
158
159    #[error("Queue error: {0}")]
160    Queue(String),
161
162    #[error("Config")]
163    Config(String),
164
165    #[error("Configuration error: {0}")]
166    Configuration(String),
167
168    #[error("Config file Error: {0}")]
169    ConfigFile(String),
170
171    #[error("Cluster presence error: {0}")]
172    ClusterPresence(String),
173
174    #[error("Dead node cleanup error: {0}")]
175    DeadNodeCleanup(String),
176}
177
178// Add conversion to WebSocket close codes
179impl Error {
180    pub fn close_code(&self) -> u16 {
181        match self {
182            // 4000-4099: Don't reconnect
183            Error::SSLRequired => 4000,
184            Error::ApplicationNotFound => 4001,
185            Error::ApplicationDisabled => 4003,
186            Error::OverConnectionQuota => 4004,
187            Error::PathNotFound => 4005,
188            Error::InvalidVersionFormat => 4006,
189            Error::UnsupportedProtocolVersion(_) => 4007,
190            Error::NoProtocolVersion => 4008,
191            Error::Unauthorized => 4009,
192            Error::OriginNotAllowed => 4009,
193
194            // 4100-4199: Reconnect with backoff
195            Error::OverCapacity => 4100,
196
197            // 4200-4299: Reconnect immediately
198            Error::ReconnectImmediately => 4200,
199            Error::PongNotReceived => 4201,
200            Error::InactivityTimeout => 4202,
201
202            // 4300-4399: Other errors
203            Error::ClientEventRateLimit => 4301,
204            Error::ClientEventRateLimitTerminate => 4301,
205            Error::WatchlistLimitExceeded => 4302,
206
207            Error::Broadcast(_) => 4303,
208
209            // Map other errors to appropriate ranges
210            Error::Channel(_)
211            | Error::InvalidChannelName(_)
212            | Error::ChannelExists
213            | Error::ChannelNotFound => 4300,
214
215            Error::ClientEvent(_) => 4301,
216
217            Error::Auth(_) | Error::InvalidSignature | Error::InvalidKey => 4009,
218
219            Error::Connection(_) | Error::ConnectionExists | Error::ConnectionNotFound => 4000,
220
221            // Buffer full - client is too slow, disconnect
222            Error::BufferFull(_) => 4100,
223
224            _ => 4000, // Default to don't reconnect for unknown errors
225        }
226    }
227
228    pub fn is_fatal(&self) -> bool {
229        matches!(
230            self,
231            Error::SSLRequired
232                | Error::ApplicationNotFound
233                | Error::ApplicationDisabled
234                | Error::OverConnectionQuota
235                | Error::PathNotFound
236                | Error::InvalidVersionFormat
237                | Error::UnsupportedProtocolVersion(_)
238                | Error::NoProtocolVersion
239                | Error::Unauthorized
240                | Error::ClientEventRateLimitTerminate
241        )
242    }
243
244    pub fn should_reconnect(&self) -> bool {
245        matches!(
246            self,
247            Error::OverCapacity
248                | Error::ReconnectImmediately
249                | Error::PongNotReceived
250                | Error::InactivityTimeout
251        )
252    }
253}
254
255// Convert to Pusher protocol error message
256impl From<Error> for sockudo_protocol::messages::ErrorData {
257    fn from(error: Error) -> Self {
258        Self {
259            code: Some(error.close_code()),
260            message: error.to_string(),
261        }
262    }
263}
264
265// Helper functions for error handling
266pub type Result<T> = std::result::Result<T, Error>;
267
268/// Health check timeout in milliseconds - centralized constant for all health checks
269pub const HEALTH_CHECK_TIMEOUT_MS: u64 = 400;
270
271// Health check status
272#[derive(Debug, Clone)]
273pub enum HealthStatus {
274    Ok,
275    Degraded(Vec<String>), // Some issues but still functional
276    Error(Vec<String>),    // Critical issues, not functional
277    NotFound,              // App doesn't exist
278}
279
280// src/error/macros.rs
281#[macro_export]
282macro_rules! ensure {
283    ($cond:expr, $err:expr) => {
284        if !($cond) {
285            return Err($err);
286        }
287    };
288}
289
290#[macro_export]
291macro_rules! bail {
292    ($err:expr) => {
293        return Err($err);
294    };
295}