signal_fish_client/error.rs
1//! Error types for the Signal Fish client.
2
3use crate::error_codes::ErrorCode;
4use thiserror::Error;
5
6/// Errors that can occur when using the Signal Fish client.
7#[derive(Debug, Error)]
8pub enum SignalFishError {
9 /// Failed to send a message through the transport.
10 #[error("transport send error: {0}")]
11 TransportSend(String),
12
13 /// Failed to receive a message from the transport.
14 #[error("transport receive error: {0}")]
15 TransportReceive(String),
16
17 /// The transport connection was closed unexpectedly.
18 #[error("transport connection closed")]
19 TransportClosed,
20
21 /// Failed to serialize or deserialize a protocol message.
22 #[error("serialization error: {0}")]
23 Serialization(#[from] serde_json::Error),
24
25 /// Attempted an operation that requires an active connection, but the client is not connected.
26 #[error("not connected to server")]
27 NotConnected,
28
29 /// The bounded outgoing command queue is full — the caller is producing
30 /// messages faster than the transport can drain them.
31 ///
32 /// This is the client's send-side backpressure signal: nothing was lost,
33 /// the message was simply refused. Either retry later (e.g. next frame),
34 /// pace high-rate payloads with a waiting `*_reliable` variant
35 /// ([`SignalFishClient::send_game_data_reliable`](crate::SignalFishClient::send_game_data_reliable),
36 /// [`SignalFishClient::send_signal_reliable`](crate::SignalFishClient::send_signal_reliable)),
37 /// or raise
38 /// [`SignalFishConfig::command_channel_capacity`](crate::SignalFishConfig::command_channel_capacity).
39 #[error(
40 "outgoing command queue full (capacity {capacity}): the transport cannot keep up; \
41 retry later, pace high-rate sends with a waiting *_reliable variant, or increase \
42 command_channel_capacity"
43 )]
44 SendBufferFull {
45 /// Configured capacity of the outgoing command queue.
46 capacity: usize,
47 },
48
49 /// Attempted a room operation but the client is not in a room.
50 #[error("not in a room")]
51 NotInRoom,
52
53 /// The server returned an error message.
54 #[error("server error: {message}")]
55 ServerError {
56 /// Human-readable error message from the server.
57 message: String,
58 /// Structured error code, if provided by the server.
59 error_code: Option<ErrorCode>,
60 },
61
62 /// A protocol-v3-only operation was attempted on a connection that has not
63 /// negotiated v3.
64 ///
65 /// The server would reject the message, so the client fails fast at the call
66 /// site instead — better UX than an asynchronous, unattributed error event.
67 /// Opt into relay/accountability v3 with
68 /// [`SignalFishConfig::enable_v3`](crate::SignalFishConfig::enable_v3), or
69 /// opt into mesh signaling with
70 /// [`SignalFishConfig::enable_mesh`](crate::SignalFishConfig::enable_mesh).
71 #[error(
72 "operation requires a negotiated protocol v3 session (current mode: {mode}); \
73 opt into v3 with SignalFishConfig::enable_v3() or SignalFishConfig::enable_mesh()"
74 )]
75 ProtocolUnsupported {
76 /// Why v3 is unavailable:
77 /// - `"relay-only"` — a `ProtocolInfo` was received but negotiated below
78 /// v3 (the v2 relay floor); waiting will not help. Enable the required
79 /// v3 capabilities and reconnect.
80 /// - `"pre-negotiation"` — no `ProtocolInfo` has been received yet;
81 /// negotiation is still in flight, so retry once it completes.
82 mode: &'static str,
83 },
84
85 /// Binary game data was requested without negotiating a binary encoding.
86 #[error(
87 "binary game data requires game_data_format=message_pack or rkyv; this connection uses JSON"
88 )]
89 BinaryFormatNotNegotiated,
90
91 /// An operation timed out.
92 #[error("operation timed out")]
93 Timeout,
94
95 /// An I/O error occurred.
96 #[error("I/O error: {0}")]
97 Io(#[from] std::io::Error),
98}
99
100/// A specialized [`Result`] type for Signal Fish client operations.
101pub type Result<T> = std::result::Result<T, SignalFishError>;
102
103#[cfg(test)]
104#[allow(
105 clippy::unwrap_used,
106 clippy::expect_used,
107 clippy::panic,
108 clippy::todo,
109 clippy::unimplemented,
110 clippy::indexing_slicing
111)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn server_error_uses_typed_error_code() {
117 let err = SignalFishError::ServerError {
118 message: "room full".into(),
119 error_code: Some(ErrorCode::RoomFull),
120 };
121
122 if let SignalFishError::ServerError {
123 message,
124 error_code,
125 } = err
126 {
127 assert_eq!(message, "room full");
128 assert_eq!(error_code, Some(ErrorCode::RoomFull));
129 } else {
130 panic!("expected ServerError");
131 }
132 }
133}