Skip to main content

rustlavel_ws/
error.rs

1//! What can go wrong once a socket is speaking WebSocket.
2//!
3//! Every variant knows which RFC 6455 close code it should be reported with,
4//! because the protocol's contract is that a connection is never just dropped:
5//! the peer is told *why* it is going away.
6
7use crate::frame::CloseCode;
8
9pub type WsResult<T> = std::result::Result<T, WsError>;
10
11#[derive(Debug)]
12pub enum WsError {
13    /// The peer broke the framing rules: a reserved bit set, an unmasked client
14    /// frame, a continuation that started nothing.
15    Protocol(String),
16    /// A frame or a reassembled message exceeded the configured limit.
17    TooLarge { size: usize, limit: usize },
18    /// A text frame whose payload was not valid UTF-8. RFC 6455 is explicit
19    /// that this closes with 1007 rather than being passed to the application.
20    InvalidUtf8,
21    /// The peer stopped answering pings within the idle timeout.
22    Idle,
23    /// The peer's send queue is full: it is not reading fast enough to keep up.
24    /// The only safe answer is to drop it, so this is a distinct variant rather
25    /// than an I/O error.
26    Full,
27    /// The connection is already closed, so there is nothing to send on.
28    Closed,
29    Io(std::io::Error),
30}
31
32impl WsError {
33    pub fn protocol(message: impl Into<String>) -> Self {
34        WsError::Protocol(message.into())
35    }
36
37    /// The close code this failure should be reported to the peer with.
38    pub fn close_code(&self) -> CloseCode {
39        match self {
40            WsError::Protocol(_) => CloseCode::PROTOCOL_ERROR,
41            WsError::TooLarge { .. } => CloseCode::TOO_LARGE,
42            WsError::InvalidUtf8 => CloseCode::INVALID_PAYLOAD,
43            WsError::Idle => CloseCode::GOING_AWAY,
44            WsError::Full => CloseCode::POLICY,
45            WsError::Closed | WsError::Io(_) => CloseCode::ABNORMAL,
46        }
47    }
48}
49
50impl std::fmt::Display for WsError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            WsError::Protocol(message) => write!(f, "websocket protocol error: {message}"),
54            WsError::TooLarge { size, limit } => {
55                write!(f, "message of {size} bytes exceeds the {limit} byte limit")
56            }
57            WsError::InvalidUtf8 => f.write_str("text frame payload was not valid UTF-8"),
58            WsError::Idle => f.write_str("peer stopped answering pings"),
59            WsError::Full => f.write_str("peer is not reading fast enough"),
60            WsError::Closed => f.write_str("the connection is closed"),
61            WsError::Io(e) => write!(f, "websocket i/o failed: {e}"),
62        }
63    }
64}
65
66impl std::error::Error for WsError {
67    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
68        match self {
69            WsError::Io(e) => Some(e),
70            _ => None,
71        }
72    }
73}
74
75impl From<std::io::Error> for WsError {
76    fn from(e: std::io::Error) -> Self {
77        WsError::Io(e)
78    }
79}
80
81/// So a handler that talks to a socket can still use `?` alongside the rest of
82/// the framework's error type.
83impl From<WsError> for rustlavel_core::Error {
84    fn from(e: WsError) -> Self {
85        match e {
86            WsError::Io(io) => rustlavel_core::Error::Io(io),
87            other => rustlavel_core::Error::Protocol(other.to_string()),
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn each_failure_maps_to_the_close_code_the_rfc_names() {
98        assert_eq!(WsError::protocol("nope").close_code(), CloseCode::PROTOCOL_ERROR);
99        assert_eq!(WsError::InvalidUtf8.close_code(), CloseCode::INVALID_PAYLOAD);
100        assert_eq!(WsError::TooLarge { size: 9, limit: 4 }.close_code(), CloseCode::TOO_LARGE);
101        assert_eq!(WsError::Idle.close_code(), CloseCode::GOING_AWAY);
102    }
103
104    #[test]
105    fn oversize_says_both_the_size_and_the_limit() {
106        let message = WsError::TooLarge { size: 2048, limit: 1024 }.to_string();
107        assert!(message.contains("2048"), "{message}");
108        assert!(message.contains("1024"), "{message}");
109    }
110}