1use serde::{Deserialize, Serialize};
7use std::fmt;
8use thiserror::Error;
9use tokio_tungstenite::tungstenite;
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub struct ConnectionId(pub Uuid);
15
16impl ConnectionId {
17 pub fn new() -> Self {
18 Self(Uuid::new_v4())
19 }
20}
21
22impl Default for ConnectionId {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl fmt::Display for ConnectionId {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "{}", self.0)
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub enum WebSocketMessage {
37 Text(String),
39 Binary(Vec<u8>),
41 Ping(Vec<u8>),
43 Pong(Vec<u8>),
45 Close(Option<CloseFrame>),
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub struct CloseFrame {
52 pub code: u16,
53 pub reason: String,
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub enum MessageType {
59 Text,
60 Binary,
61 Ping,
62 Pong,
63 Close,
64}
65
66impl WebSocketMessage {
67 pub fn text<T: Into<String>>(content: T) -> Self {
68 Self::Text(content.into())
69 }
70
71 pub fn binary<T: Into<Vec<u8>>>(data: T) -> Self {
72 Self::Binary(data.into())
73 }
74
75 pub fn ping<T: Into<Vec<u8>>>(data: T) -> Self {
76 Self::Ping(data.into())
77 }
78
79 pub fn pong<T: Into<Vec<u8>>>(data: T) -> Self {
80 Self::Pong(data.into())
81 }
82
83 pub fn close() -> Self {
84 Self::Close(None)
85 }
86
87 pub fn close_with_reason(code: u16, reason: String) -> Self {
88 Self::Close(Some(CloseFrame { code, reason }))
89 }
90
91 pub fn message_type(&self) -> MessageType {
92 match self {
93 Self::Text(_) => MessageType::Text,
94 Self::Binary(_) => MessageType::Binary,
95 Self::Ping(_) => MessageType::Ping,
96 Self::Pong(_) => MessageType::Pong,
97 Self::Close(_) => MessageType::Close,
98 }
99 }
100
101 pub fn is_text(&self) -> bool {
102 matches!(self, Self::Text(_))
103 }
104
105 pub fn is_binary(&self) -> bool {
106 matches!(self, Self::Binary(_))
107 }
108
109 pub fn is_control(&self) -> bool {
110 matches!(self, Self::Ping(_) | Self::Pong(_) | Self::Close(_))
111 }
112}
113
114impl From<tungstenite::Message> for WebSocketMessage {
116 fn from(msg: tungstenite::Message) -> Self {
117 match msg {
118 tungstenite::Message::Text(text) => Self::Text(text),
119 tungstenite::Message::Binary(data) => Self::Binary(data),
120 tungstenite::Message::Ping(data) => Self::Ping(data),
121 tungstenite::Message::Pong(data) => Self::Pong(data),
122 tungstenite::Message::Close(frame) => Self::Close(frame.map(|f| CloseFrame {
123 code: f.code.into(),
124 reason: f.reason.into(),
125 })),
126 tungstenite::Message::Frame(_) => {
127 unreachable!("Raw frames should not be exposed by tungstenite's high-level API")
129 }
130 }
131 }
132}
133
134impl From<WebSocketMessage> for tungstenite::Message {
136 fn from(msg: WebSocketMessage) -> Self {
137 match msg {
138 WebSocketMessage::Text(text) => tungstenite::Message::Text(text),
139 WebSocketMessage::Binary(data) => tungstenite::Message::Binary(data),
140 WebSocketMessage::Ping(data) => tungstenite::Message::Ping(data),
141 WebSocketMessage::Pong(data) => tungstenite::Message::Pong(data),
142 WebSocketMessage::Close(frame) => {
143 tungstenite::Message::Close(frame.map(|f| tungstenite::protocol::CloseFrame {
144 code: tungstenite::protocol::frame::coding::CloseCode::from(f.code),
145 reason: f.reason.into(),
146 }))
147 }
148 }
149 }
150}
151
152#[derive(Debug, Error)]
154pub enum WebSocketError {
155 #[error("Connection error: {0}")]
156 Connection(String),
157
158 #[error("Protocol error: {0}")]
159 Protocol(String),
160
161 #[error("IO error: {0}")]
162 Io(#[from] std::io::Error),
163
164 #[error("Serialization error: {0}")]
165 Serialization(String),
166
167 #[error("Connection closed")]
168 ConnectionClosed,
169
170 #[error("Invalid message type")]
171 InvalidMessageType,
172
173 #[error("Send queue full")]
174 SendQueueFull,
175
176 #[error("Connection not found: {0}")]
177 ConnectionNotFound(ConnectionId),
178}
179
180impl From<tungstenite::Error> for WebSocketError {
181 fn from(err: tungstenite::Error) -> Self {
182 match err {
183 tungstenite::Error::ConnectionClosed => Self::ConnectionClosed,
184 tungstenite::Error::Protocol(msg) => Self::Protocol(msg.to_string()),
185 tungstenite::Error::Io(io_err) => Self::Io(io_err),
186 other => Self::Connection(other.to_string()),
187 }
188 }
189}
190
191pub type WebSocketResult<T> = Result<T, WebSocketError>;
193
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub enum ConnectionState {
197 Connecting,
199 Connected,
201 Closing,
203 Closed,
205 Failed(String),
207}
208
209impl ConnectionState {
210 pub fn is_active(&self) -> bool {
211 matches!(self, Self::Connected)
212 }
213
214 pub fn is_closed(&self) -> bool {
215 matches!(self, Self::Closed | Self::Failed(_))
216 }
217}
218
219#[derive(Debug, Clone)]
221pub struct WebSocketConfig {
222 pub max_message_size: Option<usize>,
224 pub max_frame_size: Option<usize>,
226 pub auto_pong: bool,
228 pub ping_interval: Option<u64>,
230 pub connect_timeout: Option<u64>,
232}
233
234impl Default for WebSocketConfig {
235 fn default() -> Self {
236 Self {
237 max_message_size: Some(64 * 1024 * 1024), max_frame_size: Some(16 * 1024 * 1024), auto_pong: true,
240 ping_interval: Some(30), connect_timeout: Some(10), }
243 }
244}