use std::backtrace::{Backtrace, BacktraceStatus};
use std::panic::Location;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
NoError,
ProtocolError,
InternalError,
FlowControlError,
SettingsTimeout,
StreamClosed,
FrameSizeError,
RefusedStream,
Cancel,
CompressionError,
ConnectError,
EnhanceYourCalm,
InadequateSecurity,
Http11Required,
WebtransportError,
WebtransportStreamStateError,
WebtransportFlowControlError,
Unknown(u32),
}
impl ErrorCode {
#[must_use]
pub const fn from_u32(value: u32) -> Self {
match value {
0x00 => Self::NoError,
0x01 => Self::ProtocolError,
0x02 => Self::InternalError,
0x03 => Self::FlowControlError,
0x04 => Self::SettingsTimeout,
0x05 => Self::StreamClosed,
0x06 => Self::FrameSizeError,
0x07 => Self::RefusedStream,
0x08 => Self::Cancel,
0x09 => Self::CompressionError,
0x0a => Self::ConnectError,
0x0b => Self::EnhanceYourCalm,
0x0c => Self::InadequateSecurity,
0x0d => Self::Http11Required,
0x100 => Self::WebtransportError,
0x101 => Self::WebtransportStreamStateError,
0x102 => Self::WebtransportFlowControlError,
_ => Self::Unknown(value),
}
}
#[must_use]
pub const fn as_u32(self) -> u32 {
match self {
Self::NoError => 0x00,
Self::ProtocolError => 0x01,
Self::InternalError => 0x02,
Self::FlowControlError => 0x03,
Self::SettingsTimeout => 0x04,
Self::StreamClosed => 0x05,
Self::FrameSizeError => 0x06,
Self::RefusedStream => 0x07,
Self::Cancel => 0x08,
Self::CompressionError => 0x09,
Self::ConnectError => 0x0a,
Self::EnhanceYourCalm => 0x0b,
Self::InadequateSecurity => 0x0c,
Self::Http11Required => 0x0d,
Self::WebtransportError => 0x100,
Self::WebtransportStreamStateError => 0x101,
Self::WebtransportFlowControlError => 0x102,
Self::Unknown(code) => code,
}
}
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoError => write!(f, "NO_ERROR"),
Self::ProtocolError => write!(f, "PROTOCOL_ERROR"),
Self::InternalError => write!(f, "INTERNAL_ERROR"),
Self::FlowControlError => write!(f, "FLOW_CONTROL_ERROR"),
Self::SettingsTimeout => write!(f, "SETTINGS_TIMEOUT"),
Self::StreamClosed => write!(f, "STREAM_CLOSED"),
Self::FrameSizeError => write!(f, "FRAME_SIZE_ERROR"),
Self::RefusedStream => write!(f, "REFUSED_STREAM"),
Self::Cancel => write!(f, "CANCEL"),
Self::CompressionError => write!(f, "COMPRESSION_ERROR"),
Self::ConnectError => write!(f, "CONNECT_ERROR"),
Self::EnhanceYourCalm => write!(f, "ENHANCE_YOUR_CALM"),
Self::InadequateSecurity => write!(f, "INADEQUATE_SECURITY"),
Self::Http11Required => write!(f, "HTTP_1_1_REQUIRED"),
Self::WebtransportError => write!(f, "WEBTRANSPORT_ERROR"),
Self::WebtransportStreamStateError => write!(f, "WEBTRANSPORT_STREAM_STATE_ERROR"),
Self::WebtransportFlowControlError => write!(f, "WEBTRANSPORT_FLOW_CONTROL_ERROR"),
Self::Unknown(code) => write!(f, "UNKNOWN(0x{code:x})"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
ConnectionError(ErrorCode),
StreamError(ErrorCode),
BufferTooShort,
Incomplete,
InvalidInput,
HpackError,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConnectionError(code) => write!(f, "ConnectionError({code})"),
Self::StreamError(code) => write!(f, "StreamError({code})"),
Self::BufferTooShort => write!(f, "BufferTooShort"),
Self::Incomplete => write!(f, "Incomplete"),
Self::InvalidInput => write!(f, "InvalidInput"),
Self::HpackError => write!(f, "HpackError"),
}
}
}
pub struct Error {
pub kind: ErrorKind,
pub reason: String,
pub location: &'static Location<'static>,
pub backtrace: Backtrace,
}
impl Error {
#[track_caller]
pub fn new(kind: ErrorKind) -> Self {
Self::with_reason(kind, String::new())
}
#[track_caller]
pub fn with_reason<T: Into<String>>(kind: ErrorKind, reason: T) -> Self {
Self {
kind,
reason: reason.into(),
location: Location::caller(),
backtrace: Backtrace::capture(),
}
}
#[track_caller]
pub fn connection_error<T: Into<String>>(code: ErrorCode, reason: T) -> Self {
Self::with_reason(ErrorKind::ConnectionError(code), reason)
}
#[track_caller]
pub fn stream_error<T: Into<String>>(code: ErrorCode, reason: T) -> Self {
Self::with_reason(ErrorKind::StreamError(code), reason)
}
#[track_caller]
pub fn buffer_too_short() -> Self {
Self::new(ErrorKind::BufferTooShort)
}
#[track_caller]
pub fn incomplete() -> Self {
Self::new(ErrorKind::Incomplete)
}
#[track_caller]
pub fn invalid_input<T: Into<String>>(reason: T) -> Self {
Self::with_reason(ErrorKind::InvalidInput, reason)
}
#[track_caller]
pub fn hpack_error<T: Into<String>>(reason: T) -> Self {
Self::with_reason(ErrorKind::HpackError, reason)
}
#[track_caller]
pub fn protocol_error<T: Into<String>>(reason: T) -> Self {
Self::connection_error(ErrorCode::ProtocolError, reason)
}
#[track_caller]
pub fn frame_size_error<T: Into<String>>(reason: T) -> Self {
Self::connection_error(ErrorCode::FrameSizeError, reason)
}
#[track_caller]
pub fn check_buffer_size(required: usize, buf: &[u8]) -> Result<()> {
if buf.len() < required {
Err(Self::buffer_too_short())
} else {
Ok(())
}
}
#[must_use]
pub const fn is_connection_error(&self) -> bool {
matches!(self.kind, ErrorKind::ConnectionError(_))
}
#[must_use]
pub const fn is_stream_error(&self) -> bool {
matches!(self.kind, ErrorKind::StreamError(_))
}
#[must_use]
pub const fn error_code(&self) -> Option<ErrorCode> {
match self.kind {
ErrorKind::ConnectionError(code) | ErrorKind::StreamError(code) => Some(code),
_ => None,
}
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.kind)?;
if !self.reason.is_empty() {
write!(f, ": {}", self.reason)?;
}
write!(f, " (at {}:{})", self.location.file(), self.location.line())?;
if self.backtrace.status() == BacktraceStatus::Captured {
write!(f, "\n\nBacktrace:\n{}", self.backtrace)?;
}
Ok(())
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;