use std::net::SocketAddr;
use crate::{
Error, Result, WebSocketCloseFrame, WebSocketMessage, WebSocketSessionId, proto::Bytes,
};
pub struct WebSocketServerSession {
id: WebSocketSessionId,
peer: Option<SocketAddr>,
outbound: Vec<WebSocketMessage>,
closed: bool,
}
impl WebSocketServerSession {
#[must_use]
pub fn for_test(id: WebSocketSessionId, peer: Option<SocketAddr>) -> Self {
Self {
id,
peer,
outbound: Vec::new(),
closed: false,
}
}
#[must_use]
pub const fn id(&self) -> WebSocketSessionId {
self.id
}
#[must_use]
pub const fn peer_addr(&self) -> Option<SocketAddr> {
self.peer
}
pub fn send(&mut self, message: WebSocketMessage) -> Result<()> {
if self.closed {
return Err(Error::ConnectionClosed);
}
if matches!(message, WebSocketMessage::Close(_)) {
self.closed = true;
}
self.outbound.push(message);
Ok(())
}
pub fn send_text(&mut self, text: impl Into<String>) -> Result<()> {
self.send(WebSocketMessage::Text(text.into()))
}
pub fn send_binary(&mut self, data: impl Into<Bytes>) -> Result<()> {
self.send(WebSocketMessage::Binary(data.into()))
}
pub fn close(&mut self, close: Option<WebSocketCloseFrame>) -> Result<()> {
self.send(WebSocketMessage::Close(close))
}
#[must_use]
pub fn has_close(&self) -> bool {
self.outbound
.iter()
.any(|message| matches!(message, WebSocketMessage::Close(_)))
}
pub fn take_outbound(&mut self) -> Vec<WebSocketMessage> {
std::mem::take(&mut self.outbound)
}
}
pub trait WebSocketUpgradeHandler {
fn on_open(&mut self, session: &mut WebSocketServerSession) -> Result<()> {
let _ = session;
Ok(())
}
fn on_message(
&mut self,
session: &mut WebSocketServerSession,
message: WebSocketMessage,
) -> Result<()>;
fn on_close(
&mut self,
session: &mut WebSocketServerSession,
close: Option<WebSocketCloseFrame>,
) -> Result<()> {
let _ = (session, close);
Ok(())
}
fn on_error(&mut self, session: &mut WebSocketServerSession, error: &Error) {
let _ = (session, error);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_test_session_queues_text_without_a_socket() {
let mut session = WebSocketServerSession::for_test(WebSocketSessionId::new(4), None);
session
.send_text("ping")
.unwrap_or_else(|error| panic!("send failed: {error}"));
let queued = session.take_outbound();
assert_eq!(queued.len(), 1);
assert!(matches!(queued.first(), Some(WebSocketMessage::Text(text)) if text == "ping"));
assert!(session.take_outbound().is_empty());
}
}