use std::sync::Arc; use n0_error::stack_error; use thiserror::Error; use crate::{ConnectError, SettingsError}; /// An error returned when connecting to a WebTransport endpoint. #[stack_error(derive, from_sources)] #[derive(Clone)] pub enum ClientError { #[error("unexpected end of stream")] UnexpectedEnd, #[error("failed to connect")] Connect(#[error(source)] Arc), #[error("connection failed")] Connection(#[error(source, std_err)] iroh::endpoint::ConnectionError), #[error("failed to write")] WriteError(#[error(source, std_err)] quinn::WriteError), #[error("failed to read")] ReadError(#[error(source, std_err)] quinn::ReadError), #[error("failed to exchange h3 settings")] SettingsError(#[error(from, source, std_err)] SettingsError), #[error("failed to exchange h3 connect")] HttpError(#[error(from, source, std_err)] ConnectError), #[error("invalid URL")] InvalidUrl, #[error("endpoint failed to bind")] Bind(#[error(source)] Arc), } /// An errors returned by [`crate::Session`], split based on if they are underlying QUIC errors or WebTransport errors. #[derive(Clone, Error, Debug)] pub enum SessionError { #[error("connection error: {0}")] ConnectionError(#[from] iroh::endpoint::ConnectionError), #[error("webtransport error: {0}")] WebTransportError(#[from] WebTransportError), #[error("send datagram error: {0}")] SendDatagramError(#[from] quinn::SendDatagramError), } /// An error that can occur when reading/writing the WebTransport stream header. #[derive(Clone, Error, Debug)] pub enum WebTransportError { #[error("closed: code={0} reason={1}")] Closed(u32, String), #[error("unknown session")] UnknownSession, #[error("read error: {0}")] ReadError(#[from] quinn::ReadExactError), #[error("write error: {0}")] WriteError(#[from] quinn::WriteError), } /// An error when writing to [`crate::SendStream`]. Similar to [`quinn::WriteError`]. #[derive(Clone, Error, Debug)] pub enum WriteError { #[error("STOP_SENDING: {0}")] Stopped(u32), #[error("invalid STOP_SENDING: {0}")] InvalidStopped(quinn::VarInt), #[error("session error: {0}")] SessionError(#[from] SessionError), #[error("stream closed")] ClosedStream, } impl From for WriteError { fn from(e: quinn::WriteError) -> Self { match e { quinn::WriteError::Stopped(code) => { match web_transport_proto::error_from_http3(code.into_inner()) { Some(code) => WriteError::Stopped(code), None => WriteError::InvalidStopped(code), } } quinn::WriteError::ClosedStream => WriteError::ClosedStream, quinn::WriteError::ConnectionLost(e) => WriteError::SessionError(e.into()), quinn::WriteError::ZeroRttRejected => unreachable!("0-RTT not supported"), } } } /// An error when reading from [`crate::RecvStream`]. Similar to [`quinn::ReadError`]. #[derive(Clone, Error, Debug)] pub enum ReadError { #[error("session error: {0}")] SessionError(#[from] SessionError), #[error("RESET_STREAM: {0}")] Reset(u32), #[error("invalid RESET_STREAM: {0}")] InvalidReset(quinn::VarInt), #[error("stream already closed")] ClosedStream, } impl From for ReadError { fn from(value: quinn::ReadError) -> Self { match value { quinn::ReadError::Reset(code) => { match web_transport_proto::error_from_http3(code.into_inner()) { Some(code) => ReadError::Reset(code), None => ReadError::InvalidReset(code), } } quinn::ReadError::ConnectionLost(e) => ReadError::SessionError(e.into()), quinn::ReadError::ClosedStream => ReadError::ClosedStream, quinn::ReadError::ZeroRttRejected => unreachable!("0-RTT not supported"), } } } /// An error returned by [`crate::RecvStream::read_exact`]. Similar to [`quinn::ReadExactError`]. #[derive(Clone, Error, Debug)] pub enum ReadExactError { #[error("finished early")] FinishedEarly(usize), #[error("read error: {0}")] ReadError(#[from] ReadError), } impl From for ReadExactError { fn from(e: quinn::ReadExactError) -> Self { match e { quinn::ReadExactError::FinishedEarly(size) => ReadExactError::FinishedEarly(size), quinn::ReadExactError::ReadError(e) => ReadExactError::ReadError(e.into()), } } } /// An error returned by [`crate::RecvStream::read_to_end`]. Similar to [`quinn::ReadToEndError`]. #[derive(Clone, Error, Debug)] pub enum ReadToEndError { #[error("too long")] TooLong, #[error("read error: {0}")] ReadError(#[from] ReadError), } impl From for ReadToEndError { fn from(e: quinn::ReadToEndError) -> Self { match e { quinn::ReadToEndError::TooLong => ReadToEndError::TooLong, quinn::ReadToEndError::Read(e) => ReadToEndError::ReadError(e.into()), } } } /// An error indicating the stream was already closed. #[derive(Clone, Error, Debug)] #[error("stream closed")] pub struct ClosedStream; impl From for ClosedStream { fn from(_: quinn::ClosedStream) -> Self { ClosedStream } } /// An error returned when receiving a new WebTransport session. #[stack_error(derive, from_sources)] #[derive(Clone)] pub enum ServerError { #[error("unexpected end of stream")] UnexpectedEnd, #[error("connection failed")] Connection(#[error(source, std_err)] iroh::endpoint::ConnectionError), #[error("connection failed during handshake")] Connecting(#[error(source)] Arc), #[error("failed to write")] WriteError(#[error(source, std_err)] quinn::WriteError), #[error("failed to read")] ReadError(#[error(source, std_err)] quinn::ReadError), #[error("io error")] IoError(#[error(source)] Arc), #[error("failed to bind endpoint")] Bind(#[error(source)] Arc), #[error("failed to exchange h3 connect")] HttpError(#[error(source, from, std_err)] ConnectError), #[error("failed to exchange h3 settings")] SettingsError(#[error(source, from, std_err)] SettingsError), } impl web_transport_trait::Error for SessionError { fn session_error(&self) -> Option<(u32, String)> { if let SessionError::WebTransportError(WebTransportError::Closed(code, reason)) = self { return Some((*code, reason.to_string())); } None } } impl web_transport_trait::Error for WriteError { fn session_error(&self) -> Option<(u32, String)> { if let WriteError::SessionError(e) = self { return e.session_error(); } None } fn stream_error(&self) -> Option { match self { WriteError::Stopped(code) => Some(*code), _ => None, } } } impl web_transport_trait::Error for ReadError { fn session_error(&self) -> Option<(u32, String)> { if let ReadError::SessionError(e) = self { return e.session_error(); } None } fn stream_error(&self) -> Option { match self { ReadError::Reset(code) => Some(*code), _ => None, } } }