use std::sync::Arc;
use web_transport_proto::{VarInt, VarIntBoundsExceeded, VarIntUnexpectedEnd};
#[derive(Debug, thiserror::Error, Clone)]
pub enum Error {
#[error("invalid frame type: {0}")]
InvalidFrameType(u64),
#[error("invalid stream id")]
InvalidStreamId,
#[error("stream closed")]
StreamClosed,
#[error("connection closed: {code}: {reason}")]
ConnectionClosed { code: VarInt, reason: String },
#[error("stream reset: {0}")]
StreamReset(VarInt),
#[error("stream stop: {0}")]
StreamStop(VarInt),
#[error("frame too large")]
FrameTooLarge,
#[error("flow control error")]
FlowControlError,
#[error("stream limit exceeded")]
StreamLimitExceeded,
#[error("duplicate transport parameter: 0x{0:02x}")]
DuplicateParam(u64),
#[error("short frame")]
Short,
#[error("connection closed")]
Closed,
#[error("idle timeout")]
IdleTimeout,
#[error("invalid protocol token: {0:?}")]
InvalidProtocol(String),
#[error("invalid server name")]
InvalidServerName,
#[error("http error status: {0}")]
Http(u16),
#[error(transparent)]
Io(Arc<std::io::Error>),
#[cfg(feature = "ws")]
#[error(transparent)]
WebSocket(Arc<tokio_tungstenite::tungstenite::Error>),
#[error("datagrams not supported")]
DatagramsUnsupported,
}
impl From<VarIntUnexpectedEnd> for Error {
fn from(_: VarIntUnexpectedEnd) -> Self {
Self::Short
}
}
impl From<VarIntBoundsExceeded> for Error {
fn from(_: VarIntBoundsExceeded) -> Self {
Self::FlowControlError
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(Arc::new(err))
}
}
#[cfg(feature = "ws")]
impl From<tokio_tungstenite::tungstenite::Error> for Error {
fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
if let tokio_tungstenite::tungstenite::Error::Http(response) = &err {
return Self::Http(response.status().as_u16());
}
Self::WebSocket(Arc::new(err))
}
}
impl web_transport_trait::Error for Error {
fn session_error(&self) -> Option<(u32, String)> {
match self {
Error::ConnectionClosed { code, reason } => match code.into_inner().try_into() {
Ok(code) => Some((code, reason.clone())),
Err(_) => None,
},
_ => None,
}
}
fn stream_error(&self) -> Option<u32> {
match self {
Error::StreamReset(code) | Error::StreamStop(code) => code.into_inner().try_into().ok(),
_ => None,
}
}
}
#[cfg(all(test, feature = "ws"))]
mod tests {
use super::*;
use tokio_tungstenite::tungstenite::{self, http};
#[test]
fn preserves_http_status() {
let response = http::Response::builder()
.status(http::StatusCode::UNAUTHORIZED)
.body(None::<Vec<u8>>)
.unwrap();
let err: Error = tungstenite::Error::Http(Box::new(response)).into();
assert!(matches!(err, Error::Http(401)));
}
#[test]
fn non_http_preserves_source() {
let err: Error = tungstenite::Error::ConnectionClosed.into();
assert!(matches!(err, Error::WebSocket(_)));
}
}