use std::{
future::poll_fn,
pin::Pin,
task::{Context, Poll, ready},
};
use futures_core::Stream;
use futures_sink::Sink;
use http::HeaderValue;
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo;
use tokio_tungstenite::{WebSocketStream, tungstenite};
use topcoat_core::error::{Error, Result};
use crate::content::websocket::Message;
#[must_use]
pub struct WebSocket {
inner: WebSocketStream<TokioIo<Upgraded>>,
protocol: Option<HeaderValue>,
}
impl WebSocket {
pub(crate) fn new(
inner: WebSocketStream<TokioIo<Upgraded>>,
protocol: Option<HeaderValue>,
) -> Self {
Self { inner, protocol }
}
pub async fn recv(&mut self) -> Option<Result<Message>> {
poll_fn(|cx| Pin::new(&mut *self).poll_next(cx)).await
}
pub async fn send(&mut self, message: Message) -> Result<()> {
poll_fn(|cx| Pin::new(&mut *self).poll_ready(cx)).await?;
Pin::new(&mut *self).start_send(message)?;
poll_fn(|cx| Pin::new(&mut *self).poll_flush(cx)).await
}
pub async fn close(mut self) -> Result<()> {
poll_fn(|cx| Pin::new(&mut self).poll_close(cx)).await
}
#[must_use]
pub fn protocol(&self) -> Option<&HeaderValue> {
self.protocol.as_ref()
}
}
impl Stream for WebSocket {
type Item = Result<Message>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
let message = match ready!(Pin::new(&mut self.inner).poll_next(cx)) {
Some(Ok(message)) => message,
Some(Err(
tungstenite::Error::ConnectionClosed | tungstenite::Error::AlreadyClosed,
))
| None => return Poll::Ready(None),
Some(Err(error)) => return Poll::Ready(Some(Err(error.into()))),
};
if let Some(message) = Message::from_tungstenite(message) {
return Poll::Ready(Some(Ok(message)));
}
}
}
}
impl Sink<Message> for WebSocket {
type Error = Error;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
Pin::new(&mut self.inner).poll_ready(cx).map_err(Into::into)
}
fn start_send(mut self: Pin<&mut Self>, message: Message) -> Result<()> {
Pin::new(&mut self.inner)
.start_send(message.into_tungstenite())
.map_err(Into::into)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx).map_err(Into::into)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
Pin::new(&mut self.inner).poll_close(cx).map_err(Into::into)
}
}