use std::{
fmt,
future::{ready, Future},
io,
};
use http_kit::header::{HeaderMap, HeaderValue, SEC_WEBSOCKET_PROTOCOL};
use skyzen_core::Extractor;
pub type WebSocketResult<T> = Result<T, WebSocketError>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WebSocketCloseFrame {
pub code: u16,
pub reason: String,
}
impl WebSocketCloseFrame {
pub fn new(code: u16, reason: impl Into<String>) -> Self {
Self {
code,
reason: reason.into(),
}
}
}
#[derive(Debug)]
pub enum WebSocketError {
Transport(io::Error),
Protocol(String),
MessageTooLarge {
len: usize,
limit: usize,
},
}
impl From<io::Error> for WebSocketError {
fn from(error: io::Error) -> Self {
Self::Transport(error)
}
}
impl fmt::Display for WebSocketError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Transport(err) => write!(f, "transport error: {err}"),
Self::Protocol(err) => write!(f, "protocol error: {err}"),
Self::MessageTooLarge { len, limit } => write!(
f,
"message of {len} bytes exceeds the configured maximum of {limit} bytes"
),
}
}
}
impl std::error::Error for WebSocketError {}
impl http_kit::HttpError for WebSocketError {}
#[cfg(feature = "json")]
impl From<serde_json::Error> for WebSocketError {
fn from(error: serde_json::Error) -> Self {
Self::Protocol(error.to_string())
}
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn offered_protocols(headers: &HeaderMap) -> Vec<String> {
headers
.get(SEC_WEBSOCKET_PROTOCOL)
.and_then(|value| value.to_str().ok())
.map(|value| {
value
.split(',')
.map(|protocol| protocol.trim().to_owned())
.filter(|protocol| !protocol.is_empty())
.collect()
})
.unwrap_or_default()
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn select_protocol(offered: &[String], supported: &[String]) -> Option<HeaderValue> {
let selected = offered
.iter()
.find(|protocol| supported.contains(protocol))?;
HeaderValue::from_str(selected).ok()
}
#[allow(clippy::redundant_pub_crate)]
pub(crate) fn select_offered_protocol(
headers: &HeaderMap,
supported: &[String],
) -> Option<HeaderValue> {
select_protocol(&offered_protocols(headers), supported)
}
#[derive(Debug, Clone, Default)]
pub struct RequestedSubprotocols(Vec<String>);
impl RequestedSubprotocols {
pub fn iter(&self) -> impl Iterator<Item = &str> {
self.0.iter().map(String::as_str)
}
#[must_use]
pub fn as_slice(&self) -> &[String] {
&self.0
}
#[must_use]
pub fn answer(&self, mut accept: impl FnMut(&str) -> bool) -> Option<HeaderValue> {
let selected = self.0.iter().find(|protocol| accept(protocol))?;
HeaderValue::from_str(selected).ok()
}
}
impl Extractor for RequestedSubprotocols {
type Error = core::convert::Infallible;
fn extract(
request: &mut crate::Request,
) -> impl Future<Output = Result<Self, Self::Error>> + Send {
ready(Ok(Self(offered_protocols(request.headers()))))
}
}