use std::{error::Error, fmt, io};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportError {
Closed { code: u64 },
Transport,
Timeout,
Reset { code: u64 },
Stopped { code: u64 },
Other,
}
impl fmt::Display for TransportError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TransportError::Closed { code } => write!(f, "connection closed with code {code:#x}"),
TransportError::Transport => write!(f, "transport closed the connection"),
TransportError::Timeout => write!(f, "transport timed out"),
TransportError::Reset { code } => write!(f, "stream reset with code {code:#x}"),
TransportError::Stopped { code } => write!(f, "stream stopped with code {code:#x}"),
TransportError::Other => write!(f, "transport error"),
}
}
}
impl Error for TransportError {}
impl From<TransportError> for io::Error {
#[inline]
fn from(err: TransportError) -> io::Error {
match err {
TransportError::Closed { .. } => io::Error::new(io::ErrorKind::ConnectionAborted, err),
TransportError::Reset { .. } | TransportError::Stopped { .. } => {
io::Error::new(io::ErrorKind::ConnectionReset, err)
}
TransportError::Timeout => io::Error::new(io::ErrorKind::TimedOut, err),
TransportError::Transport | TransportError::Other => io::Error::other(err),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H3Error {
NoError,
GeneralProtocol,
Internal,
StreamCreation,
ClosedCriticalStream,
FrameUnexpected,
FrameError,
ExcessiveLoad,
Id,
Settings,
MissingSettings,
RequestRejected,
RequestCancelled,
RequestIncomplete,
Message,
Connect,
VersionFallback,
}
impl H3Error {
pub const fn code(self) -> u64 {
use H3Error::*;
match self {
NoError => 0x0100,
GeneralProtocol => 0x0101,
Internal => 0x0102,
StreamCreation => 0x0103,
ClosedCriticalStream => 0x0104,
FrameUnexpected => 0x0105,
FrameError => 0x0106,
ExcessiveLoad => 0x0107,
Id => 0x0108,
Settings => 0x0109,
MissingSettings => 0x010a,
RequestRejected => 0x010b,
RequestCancelled => 0x010c,
RequestIncomplete => 0x010d,
Message => 0x010e,
Connect => 0x010f,
VersionFallback => 0x0110,
}
}
pub const fn from_code(code: u64) -> Option<H3Error> {
use H3Error::*;
Some(match code {
0x0100 => NoError,
0x0101 => GeneralProtocol,
0x0102 => Internal,
0x0103 => StreamCreation,
0x0104 => ClosedCriticalStream,
0x0105 => FrameUnexpected,
0x0106 => FrameError,
0x0107 => ExcessiveLoad,
0x0108 => Id,
0x0109 => Settings,
0x010a => MissingSettings,
0x010b => RequestRejected,
0x010c => RequestCancelled,
0x010d => RequestIncomplete,
0x010e => Message,
0x010f => Connect,
0x0110 => VersionFallback,
_ => return None,
})
}
}
impl fmt::Display for H3Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} ({:#06x})", self, self.code())
}
}
impl Error for H3Error {}
impl From<H3Error> for io::Error {
#[inline]
fn from(err: H3Error) -> io::Error {
io::Error::other(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc_9114_codes_round_trip() {
let table = [
(H3Error::NoError, 0x0100),
(H3Error::GeneralProtocol, 0x0101),
(H3Error::Internal, 0x0102),
(H3Error::StreamCreation, 0x0103),
(H3Error::ClosedCriticalStream, 0x0104),
(H3Error::FrameUnexpected, 0x0105),
(H3Error::FrameError, 0x0106),
(H3Error::ExcessiveLoad, 0x0107),
(H3Error::Id, 0x0108),
(H3Error::Settings, 0x0109),
(H3Error::MissingSettings, 0x010a),
(H3Error::RequestRejected, 0x010b),
(H3Error::RequestCancelled, 0x010c),
(H3Error::RequestIncomplete, 0x010d),
(H3Error::Message, 0x010e),
(H3Error::Connect, 0x010f),
(H3Error::VersionFallback, 0x0110),
];
for (err, code) in table {
assert_eq!(err.code(), code, "{err:?} code");
assert_eq!(H3Error::from_code(code), Some(err));
}
assert_eq!(H3Error::from_code(0x00ff), None);
assert_eq!(H3Error::from_code(0x0111), None);
assert_eq!(H3Error::from_code(0x0200), None); }
#[test]
fn qpack_family_is_separate() {
assert_eq!(
crate::h3::qpack::QpackError::DecompressionFailed.code(),
0x0200
);
assert_eq!(crate::h3::qpack::QpackError::EncoderStream.code(), 0x0201);
assert_eq!(crate::h3::qpack::QpackError::DecoderStream.code(), 0x0202);
}
#[test]
fn transport_error_to_io_kinds() {
let err: io::Error = TransportError::Closed { code: 0x0100 }.into();
assert_eq!(err.kind(), io::ErrorKind::ConnectionAborted);
let err: io::Error = TransportError::Reset { code: 0x010c }.into();
assert_eq!(err.kind(), io::ErrorKind::ConnectionReset);
let err: io::Error = TransportError::Stopped { code: 0x010c }.into();
assert_eq!(err.kind(), io::ErrorKind::ConnectionReset);
let err: io::Error = TransportError::Timeout.into();
assert_eq!(err.kind(), io::ErrorKind::TimedOut);
let err: io::Error = TransportError::Transport.into();
assert_eq!(err.kind(), io::ErrorKind::Other);
let err: io::Error = TransportError::Other.into();
assert_eq!(err.kind(), io::ErrorKind::Other);
}
#[test]
fn h3_error_to_io_mentions_code() {
let err: io::Error = H3Error::FrameUnexpected.into();
let text = err.to_string();
assert!(text.contains("0x0105"), "got: {text}");
assert!(text.contains("FrameUnexpected"), "got: {text}");
}
}