elif_http/websocket/
types.rs

1//! WebSocket types and abstractions for elif framework
2//!
3//! These types provide a clean, framework-native API while using tokio-tungstenite
4//! for maximum performance under the hood.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use thiserror::Error;
9use tokio_tungstenite::tungstenite;
10use uuid::Uuid;
11
12/// Unique identifier for WebSocket connections
13#[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/// WebSocket message types - clean API over tungstenite
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub enum WebSocketMessage {
37    /// Text message
38    Text(String),
39    /// Binary message
40    Binary(Vec<u8>),
41    /// Ping frame
42    Ping(Vec<u8>),
43    /// Pong frame
44    Pong(Vec<u8>),
45    /// Close frame
46    Close(Option<CloseFrame>),
47}
48
49/// Close frame information
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub struct CloseFrame {
52    pub code: u16,
53    pub reason: String,
54}
55
56/// Message type for routing and handling
57#[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
114// Conversion from tungstenite message to elif message
115impl 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                // Raw frames are internal to tungstenite and should never reach application code
128                unreachable!("Raw frames should not be exposed by tungstenite's high-level API")
129            }
130        }
131    }
132}
133
134// Conversion from elif message to tungstenite message
135impl 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/// WebSocket errors - clean API over tungstenite errors
153#[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
191/// Result type for WebSocket operations
192pub type WebSocketResult<T> = Result<T, WebSocketError>;
193
194/// Connection state tracking
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub enum ConnectionState {
197    /// Connection is being established
198    Connecting,
199    /// Connection is active and ready
200    Connected,
201    /// Connection is closing
202    Closing,
203    /// Connection is closed
204    Closed,
205    /// Connection failed
206    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/// WebSocket protocol configuration
220#[derive(Debug, Clone)]
221pub struct WebSocketConfig {
222    /// Maximum message size in bytes
223    pub max_message_size: Option<usize>,
224    /// Maximum frame size in bytes  
225    pub max_frame_size: Option<usize>,
226    /// Enable automatic ping/pong handling
227    pub auto_pong: bool,
228    /// Ping interval in seconds
229    pub ping_interval: Option<u64>,
230    /// Connection timeout in seconds
231    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), // 64MB
238            max_frame_size: Some(16 * 1024 * 1024),   // 16MB
239            auto_pong: true,
240            ping_interval: Some(30),   // 30 seconds
241            connect_timeout: Some(10), // 10 seconds
242        }
243    }
244}