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    #[error("{message}")]
110    AiTransport {
111        code: u32,
112        name: &'static str,
113        message: String,
114    },
115
116    // WebSocket errors
117    #[error("WebSocket error: {0}")]
118    WebSocket(#[from] sockudo_ws::Error),
119
120    // Internal errors
121    #[error("Internal server error: {0}")]
122    Internal(String),
123
124    // JSON serialization/deserialization errors
125    #[error("JSON error: {0}")]
126    Json(#[from] sonic_rs::Error),
127
128    #[error("Client event error: {0}")]
129    ClientEvent(String),
130
131    // I/O errors
132    #[error("I/O error: {0}")]
133    Io(#[from] std::io::Error),
134
135    // Generic errors
136    #[error("Invalid app key")]
137    InvalidAppKey,
138
139    #[error("Cache error: {0}")]
140    Cache(String),
141
142    #[error("Invalid JSON")]
143    Serialization(String),
144
145    #[error("Broadcast error: {0}")]
146    Broadcast(String),
147
148    #[error("Other: {0}")]
149    Other(String),
150
151    #[error("Redis error: {0}")]
152    Redis(String),
153
154    #[error("Request timeout")]
155    RequestTimeout,
156
157    #[error("Own request ignored")]
158    OwnRequestIgnored,
159
160    #[error("Request not for this node")]
161    RequestNotForThisNode,
162
163    #[error("Horizontal adapter error: {0}")]
164    HorizontalAdapter(String),
165
166    #[error("Queue error: {0}")]
167    Queue(String),
168
169    #[error("Config")]
170    Config(String),
171
172    #[error("Configuration error: {0}")]
173    Configuration(String),
174
175    #[error("Config file Error: {0}")]
176    ConfigFile(String),
177
178    #[error("Cluster presence error: {0}")]
179    ClusterPresence(String),
180
181    #[error("Dead node cleanup error: {0}")]
182    DeadNodeCleanup(String),
183}
184
185// Add conversion to WebSocket close codes
186impl Error {
187    pub fn close_code(&self) -> u16 {
188        match self {
189            // 4000-4099: Don't reconnect
190            Error::SSLRequired => 4000,
191            Error::ApplicationNotFound => 4001,
192            Error::ApplicationDisabled => 4003,
193            Error::OverConnectionQuota => 4004,
194            Error::PathNotFound => 4005,
195            Error::InvalidVersionFormat => 4006,
196            Error::UnsupportedProtocolVersion(_) => 4007,
197            Error::NoProtocolVersion => 4008,
198            Error::Unauthorized => 4009,
199            Error::OriginNotAllowed => 4009,
200
201            // 4100-4199: Reconnect with backoff
202            Error::OverCapacity => 4100,
203
204            // 4200-4299: Reconnect immediately
205            Error::ReconnectImmediately => 4200,
206            Error::PongNotReceived => 4201,
207            Error::InactivityTimeout => 4202,
208
209            // 4300-4399: Other errors
210            Error::ClientEventRateLimit => 4301,
211            Error::ClientEventRateLimitTerminate => 4301,
212            Error::WatchlistLimitExceeded => 4302,
213
214            Error::Broadcast(_) => 4303,
215
216            // Map other errors to appropriate ranges
217            Error::Channel(_)
218            | Error::InvalidChannelName(_)
219            | Error::ChannelExists
220            | Error::ChannelNotFound => 4300,
221
222            Error::ClientEvent(_) => 4301,
223
224            Error::AiTransport { .. } => 4000,
225
226            Error::Auth(_) | Error::InvalidSignature | Error::InvalidKey => 4009,
227
228            Error::Connection(_) | Error::ConnectionExists | Error::ConnectionNotFound => 4000,
229
230            // Buffer full - client is too slow, disconnect
231            Error::BufferFull(_) => 4100,
232
233            _ => 4000, // Default to don't reconnect for unknown errors
234        }
235    }
236
237    pub fn is_fatal(&self) -> bool {
238        matches!(
239            self,
240            Error::SSLRequired
241                | Error::ApplicationNotFound
242                | Error::ApplicationDisabled
243                | Error::OverConnectionQuota
244                | Error::PathNotFound
245                | Error::InvalidVersionFormat
246                | Error::UnsupportedProtocolVersion(_)
247                | Error::NoProtocolVersion
248                | Error::Unauthorized
249                | Error::ClientEventRateLimitTerminate
250        )
251    }
252
253    pub fn should_reconnect(&self) -> bool {
254        matches!(
255            self,
256            Error::OverCapacity
257                | Error::ReconnectImmediately
258                | Error::PongNotReceived
259                | Error::InactivityTimeout
260        )
261    }
262}
263
264// Convert to Pusher protocol error message
265impl From<Error> for sockudo_protocol::messages::ErrorData {
266    fn from(error: Error) -> Self {
267        if let Error::AiTransport { code, message, .. } = error {
268            return Self {
269                code: Some(code),
270                message,
271            };
272        }
273        Self {
274            code: Some(u32::from(error.close_code())),
275            message: error.to_string(),
276        }
277    }
278}
279
280// Helper functions for error handling
281pub type Result<T> = std::result::Result<T, Error>;
282
283// Health check status
284#[derive(Debug, Clone)]
285pub enum HealthStatus {
286    Ok,
287    Degraded(Vec<String>), // Some issues but still functional
288    Error(Vec<String>),    // Critical issues, not functional
289    NotFound,              // App doesn't exist
290}
291
292// src/error/macros.rs
293#[macro_export]
294macro_rules! ensure {
295    ($cond:expr, $err:expr) => {
296        if !($cond) {
297            return Err($err);
298        }
299    };
300}
301
302#[macro_export]
303macro_rules! bail {
304    ($err:expr) => {
305        return Err($err);
306    };
307}