use crate::{coding, serve};
#[derive(thiserror::Error, Debug, Clone)]
pub enum SessionError {
#[error("webtransport error: {0}")]
WebTransport(#[from] web_transport::Error),
#[error("encode error: {0}")]
Encode(#[from] coding::EncodeError),
#[error("decode error: {0}")]
Decode(#[from] coding::DecodeError),
#[error("role violation")]
RoleViolation,
#[error("varint bounds exceeded")]
BoundsExceeded(#[from] coding::BoundsExceeded),
#[error("duplicate")]
Duplicate,
#[error("internal error")]
Internal,
#[error("serve error: {0}")]
Serve(#[from] serve::ServeError),
#[error("wrong size")]
WrongSize,
#[error("invalid connection path: {0}")]
InvalidPath(String),
#[error("invalid request ID")]
InvalidRequestId,
#[error("too many requests")]
TooManyRequests,
#[error("protocol violation: {0}")]
ProtocolViolation(String),
}
impl SessionError {
pub fn code(&self) -> u64 {
match self {
Self::RoleViolation => 0x3,
Self::WebTransport(_) => 0x1,
Self::Encode(_) => 0x1,
Self::BoundsExceeded(_) => 0x1,
Self::Internal => 0x1,
Self::Decode(_) => 0x3,
Self::WrongSize => 0x3,
Self::InvalidPath(_) => 0x3,
Self::Duplicate => 0x5,
Self::InvalidRequestId => 0x4,
Self::TooManyRequests => 0x7,
Self::ProtocolViolation(_) => 0x3,
Self::Serve(err) => err.code(),
}
}
pub fn unimplemented(feature: &str) -> Self {
Self::Serve(serve::ServeError::not_implemented_ctx(feature))
}
pub fn is_graceful_close(&self) -> bool {
match self {
Self::WebTransport(wt_err) => match wt_err {
web_transport::Error::Session(session_err) => {
is_session_error_graceful(session_err)
}
web_transport::Error::Read(read_err) => {
if let web_transport::quinn::ReadError::SessionError(session_err) = read_err {
return is_session_error_graceful(session_err);
}
false
}
web_transport::Error::Write(write_err) => {
if let web_transport::quinn::WriteError::SessionError(session_err) = write_err {
return is_session_error_graceful(session_err);
}
false
}
_ => false,
},
_ => false,
}
}
pub(crate) fn is_stream_error(&self) -> bool {
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
{
matches!(
self,
Self::WebTransport(
web_transport::Error::Write(
web_transport::quinn::WriteError::Stopped(_)
| web_transport::quinn::WriteError::ClosedStream
) | web_transport::Error::Read(
web_transport::quinn::ReadError::Reset(_)
| web_transport::quinn::ReadError::ClosedStream
)
)
)
}
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
{
matches!(self, Self::WebTransport(web_transport::Error::Stream(_)))
}
}
}
impl From<SessionError> for serve::ServeError {
fn from(err: SessionError) -> Self {
match err {
SessionError::Serve(err) => err,
_ => serve::ServeError::internal_ctx(format!("session error: {}", err)),
}
}
}
fn is_session_error_graceful(err: &web_transport::quinn::SessionError) -> bool {
use web_transport::quinn::{SessionError, WebTransportError};
match err {
SessionError::ConnectionError(conn_err) => is_connection_error_graceful(conn_err),
SessionError::WebTransportError(WebTransportError::Closed(0, _)) => true,
SessionError::WebTransportError(_) => false,
SessionError::SendDatagramError(_) => false,
}
}
fn is_connection_error_graceful(err: &web_transport::quinn::quinn::ConnectionError) -> bool {
use web_transport::quinn::quinn::ConnectionError;
match err {
ConnectionError::ApplicationClosed(close) => {
let code = close.error_code.into_inner();
if code == 0 {
return true;
}
if let Some(wt_code) = web_transport::quinn::proto::error_from_http3(code) {
return wt_code == 0;
}
false
}
ConnectionError::LocallyClosed => true,
_ => false,
}
}
#[cfg(all(test, any(not(target_arch = "wasm32"), target_os = "wasi")))]
mod tests {
use super::*;
#[test]
fn stream_errors_are_not_session_errors() {
let reset = SessionError::WebTransport(web_transport::Error::Read(
web_transport::quinn::ReadError::Reset(42),
));
let stopped = SessionError::WebTransport(web_transport::Error::Write(
web_transport::quinn::WriteError::Stopped(42),
));
assert!(reset.is_stream_error());
assert!(stopped.is_stream_error());
assert!(!SessionError::Internal.is_stream_error());
}
}