Skip to main content

signal_fish_client/
error_codes.rs

1//! Error codes for structured error handling in the Signal Fish protocol.
2//!
3//! These codes are wire-compatible with the server's `ErrorCode` enum and
4//! serialize using `SCREAMING_SNAKE_CASE` to match the server's JSON format.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Structured error codes returned by the Signal Fish server.
10///
11/// Each variant corresponds to a specific error condition. The server sends these
12/// as `"SCREAMING_SNAKE_CASE"` strings (e.g., `"ROOM_NOT_FOUND"`).
13///
14/// Use [`description()`](ErrorCode::description) for a human-readable explanation.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
17pub enum ErrorCode {
18    // Authentication errors
19    Unauthorized,
20    InvalidToken,
21    AuthenticationRequired,
22    InvalidAppId,
23    AppIdExpired,
24    AppIdRevoked,
25    AppIdSuspended,
26    MissingAppId,
27    AuthenticationTimeout,
28    SdkVersionUnsupported,
29    UnsupportedGameDataFormat,
30
31    // Validation errors
32    InvalidInput,
33    InvalidGameName,
34    InvalidRoomCode,
35    InvalidPlayerName,
36    InvalidMaxPlayers,
37    MessageTooLarge,
38
39    // Room errors
40    RoomNotFound,
41    RoomFull,
42    AlreadyInRoom,
43    NotInRoom,
44    RoomCreationFailed,
45    MaxRoomsPerGameExceeded,
46    InvalidRoomState,
47
48    // Authority errors
49    AuthorityNotSupported,
50    AuthorityConflict,
51    AuthorityDenied,
52
53    // Rate limiting
54    RateLimitExceeded,
55    TooManyConnections,
56
57    // Reconnection errors
58    ReconnectionFailed,
59    ReconnectionTokenInvalid,
60    ReconnectionExpired,
61    PlayerAlreadyConnected,
62
63    // Spectator errors
64    SpectatorNotAllowed,
65    TooManySpectators,
66    NotASpectator,
67    SpectatorJoinFailed,
68
69    // Server errors
70    InternalError,
71    StorageError,
72    ServiceUnavailable,
73
74    // Game-start errors (protocol v2)
75    GameStartNotReady,
76    GameStartForbidden,
77
78    // Signaling errors (protocol v3)
79    CrossRoomSignal,
80    UnsupportedTransport,
81    SignalTargetNotFound,
82    SignalRateLimited,
83    SignalTooLarge,
84
85    // Connection lifecycle (protocol v3)
86    ConnectionIdleTimeout,
87
88    // Delivery & liveness
89    /// The server evicted this connection because its outbound queue stayed
90    /// full past the slow-consumer grace window (5 seconds by default): the
91    /// client was not draining messages fast enough.
92    ///
93    /// The farewell `Error` frame carrying this code is written best-effort
94    /// into an already-congested socket, so it may never arrive; a bare
95    /// disconnect can be the only observable signal. Wire: `"SLOW_CONSUMER"`.
96    SlowConsumer,
97    /// The server closed the connection after prolonged protocol inactivity
98    /// (no messages received within the activity window). Wire:
99    /// `"ACTIVITY_TIMEOUT"`.
100    ActivityTimeout,
101    /// The server is draining for shutdown and rejecting new room creation.
102    /// Existing connections close with semantic code 4000 at the deadline.
103    ServerDraining,
104    /// The requested protocol-v3 delivery class/key combination is invalid.
105    InvalidDeliveryClass,
106}
107
108impl ErrorCode {
109    /// Returns a human-readable description of this error code.
110    ///
111    /// This method provides actionable error messages that SDK developers
112    /// can display to end users or use for debugging.
113    pub fn description(&self) -> &'static str {
114        match self {
115            // Authentication errors
116            Self::Unauthorized => {
117                "Access denied. Authentication credentials are missing or invalid."
118            }
119            Self::InvalidToken => {
120                "The authentication token is invalid, malformed, or has expired. Please obtain a new token."
121            }
122            Self::AuthenticationRequired => {
123                "This operation requires authentication. Please provide valid credentials."
124            }
125            Self::InvalidAppId => {
126                "The provided application ID is not recognized. Verify your app ID is correct."
127            }
128            Self::AppIdExpired => {
129                "The application ID has expired. Please renew your application registration."
130            }
131            Self::AppIdRevoked => {
132                "The application ID has been revoked. Contact the administrator for assistance."
133            }
134            Self::AppIdSuspended => {
135                "The application ID has been suspended. Contact the administrator for assistance."
136            }
137            Self::MissingAppId => {
138                "Application ID is required but was not provided. Include your app ID in the request."
139            }
140            Self::AuthenticationTimeout => {
141                "Authentication took too long to complete. Please try again."
142            }
143            Self::SdkVersionUnsupported => {
144                "The SDK version you are using is no longer supported. Please upgrade to the latest version."
145            }
146            Self::UnsupportedGameDataFormat => {
147                "The requested game data format is not supported by this server. Falling back to JSON encoding."
148            }
149
150            // Validation errors
151            Self::InvalidInput => {
152                "The provided input is invalid or malformed. Check your request parameters."
153            }
154            Self::InvalidGameName => {
155                "The game name is invalid. Game names must be non-empty and follow naming requirements."
156            }
157            Self::InvalidRoomCode => {
158                "The room code is invalid or malformed. Room codes must follow the required format."
159            }
160            Self::InvalidPlayerName => {
161                "The player name is invalid. Player names must be non-empty and meet length requirements."
162            }
163            Self::InvalidMaxPlayers => {
164                "The maximum player count is invalid. It must be a positive number within allowed limits."
165            }
166            Self::MessageTooLarge => {
167                "The message size exceeds the maximum allowed limit. Please send a smaller message."
168            }
169
170            // Room errors
171            Self::RoomNotFound => {
172                "The requested room could not be found. It may have been closed or the code is incorrect."
173            }
174            Self::RoomFull => {
175                "The room has reached its maximum player capacity. Try joining a different room."
176            }
177            Self::AlreadyInRoom => {
178                "You are already in a room. Leave the current room before joining another."
179            }
180            Self::NotInRoom => {
181                "You are not currently in any room. Join a room before performing this action."
182            }
183            Self::RoomCreationFailed => {
184                "Failed to create the room. Please try again or contact support if the issue persists."
185            }
186            Self::MaxRoomsPerGameExceeded => {
187                "The maximum number of rooms for this game has been reached. Please try again later."
188            }
189            Self::InvalidRoomState => {
190                "The room is in an invalid state for this operation. Try refreshing or rejoining the room."
191            }
192
193            // Authority errors
194            Self::AuthorityNotSupported => {
195                "Authority features are not enabled on this server. Check your server configuration."
196            }
197            Self::AuthorityConflict => {
198                "Another client has already claimed authority. Only one client can have authority at a time."
199            }
200            Self::AuthorityDenied => {
201                "You do not have permission to claim authority in this room."
202            }
203
204            // Rate limiting
205            Self::RateLimitExceeded => {
206                "Too many requests in a short time. Please slow down and try again later."
207            }
208            Self::TooManyConnections => {
209                "You have too many active connections. Close some connections before opening new ones."
210            }
211
212            // Reconnection errors
213            Self::ReconnectionFailed => {
214                "Failed to reconnect to the room. The session may have expired or the room may be closed."
215            }
216            Self::ReconnectionTokenInvalid => {
217                "The reconnection token is invalid or malformed. You may need to join the room again."
218            }
219            Self::ReconnectionExpired => {
220                "The reconnection window has expired. You must join the room again as a new player."
221            }
222            Self::PlayerAlreadyConnected => {
223                "This player is already connected to the room from another session."
224            }
225
226            // Spectator errors
227            Self::SpectatorNotAllowed => {
228                "Spectator mode is not enabled for this room. Only players can join."
229            }
230            Self::TooManySpectators => {
231                "The room has reached its maximum spectator capacity. Try again later."
232            }
233            Self::NotASpectator => {
234                "You are not a spectator in this room. This action is only available to spectators."
235            }
236            Self::SpectatorJoinFailed => {
237                "Failed to join as a spectator. The room may be full or spectating may be disabled."
238            }
239
240            // Server errors
241            Self::InternalError => {
242                "An internal server error occurred. Please try again or contact support if the issue persists."
243            }
244            Self::StorageError => {
245                "A storage error occurred while processing your request. Please try again later."
246            }
247            Self::ServiceUnavailable => {
248                "The service is temporarily unavailable. Please try again in a few moments."
249            }
250
251            // Game-start errors (protocol v2)
252            Self::GameStartNotReady => {
253                "Cannot start the game: not every player in the room is ready yet."
254            }
255            Self::GameStartForbidden => {
256                "You are not permitted to start the game. Only the room's authority may start it."
257            }
258
259            // Signaling errors (protocol v3)
260            Self::CrossRoomSignal => {
261                "The signal targets a peer that is not in your room."
262            }
263            Self::UnsupportedTransport => {
264                "The requested data-path transport is not supported or was not negotiated for this connection."
265            }
266            Self::SignalTargetNotFound => {
267                "The signal's target peer could not be found in the room."
268            }
269            Self::SignalRateLimited => {
270                "Too many signaling messages were sent in a short time. Please slow down and try again."
271            }
272            Self::SignalTooLarge => {
273                "The signal payload exceeds the maximum size allowed by the server."
274            }
275
276            // Connection lifecycle (protocol v3)
277            Self::ConnectionIdleTimeout => {
278                "The connection was closed by the server after being idle for too long."
279            }
280
281            // Delivery & liveness
282            Self::SlowConsumer => {
283                "The server closed this connection because the client was not reading messages fast enough. Drain events promptly, or reduce inbound volume."
284            }
285            Self::ActivityTimeout => {
286                "The connection was closed by the server due to prolonged inactivity. Send periodic pings to keep the connection alive."
287            }
288            Self::ServerDraining => {
289                "The server is shutting down and is not accepting new rooms. Reconnect after the advertised drain deadline."
290            }
291            Self::InvalidDeliveryClass => {
292                "The requested game-data delivery class and key combination is invalid. Latest requires a key; reliable and volatile forbid one."
293            }
294        }
295    }
296}
297
298impl fmt::Display for ErrorCode {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(f, "{}", self.description())
301    }
302}