Skip to main content

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}