1use crate::frame::CloseCode;
8
9pub type WsResult<T> = std::result::Result<T, WsError>;
10
11#[derive(Debug)]
12pub enum WsError {
13 Protocol(String),
16 TooLarge { size: usize, limit: usize },
18 InvalidUtf8,
21 Idle,
23 Full,
27 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 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
81impl 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}