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