use thiserror::Error;
#[derive(Debug, Error)]
pub enum QuicError {
#[error("connection error: {0}")]
Connection(#[from] quinn::ConnectionError),
#[error("connect error: {0}")]
Connect(#[from] quinn::ConnectError),
#[error("write error: {0}")]
Write(#[from] quinn::WriteError),
#[error("read error: {0}")]
Read(#[from] quinn::ReadExactError),
#[error("read error: {0}")]
ReadStream(#[from] quinn::ReadError),
#[error("read to end error: {0}")]
ReadToEnd(#[from] quinn::ReadToEndError),
#[error("datagram send error: {0}")]
Datagram(#[from] quinn::SendDatagramError),
#[error("stream closed")]
StreamClosed,
#[error("endpoint closed")]
EndpointClosed,
#[error("cancelled")]
Cancelled,
#[error("TLS config error: {0}")]
TlsConfig(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("invalid configuration: {0}")]
Config(String),
#[error("failed to open stream")]
OpenStream,
}
impl QuicError {
#[must_use]
pub fn is_cancelled(&self) -> bool {
matches!(self, Self::Cancelled)
}
#[must_use]
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::StreamClosed
| Self::Read(_)
| Self::ReadStream(_)
| Self::ReadToEnd(_)
| Self::Write(_)
)
}
#[must_use]
pub fn is_connection_error(&self) -> bool {
matches!(self, Self::Connection(_) | Self::EndpointClosed)
}
}
impl From<quinn::ClosedStream> for QuicError {
fn from(_: quinn::ClosedStream) -> Self {
Self::StreamClosed
}
}
impl From<crate::error::Error> for QuicError {
fn from(err: crate::error::Error) -> Self {
if err.is_cancelled() {
Self::Cancelled
} else {
Self::Io(std::io::Error::other(err.to_string()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cancelled_is_cancelled() {
let err = QuicError::Cancelled;
assert!(err.is_cancelled());
assert!(!err.is_recoverable());
assert!(!err.is_connection_error());
}
#[test]
fn stream_closed_is_recoverable() {
let err = QuicError::StreamClosed;
assert!(err.is_recoverable());
assert!(!err.is_cancelled());
assert!(!err.is_connection_error());
}
#[test]
fn endpoint_closed_is_connection_error() {
let err = QuicError::EndpointClosed;
assert!(err.is_connection_error());
assert!(!err.is_cancelled());
assert!(!err.is_recoverable());
}
#[test]
fn open_stream_not_recoverable() {
let err = QuicError::OpenStream;
assert!(!err.is_recoverable());
assert!(!err.is_cancelled());
assert!(!err.is_connection_error());
}
#[test]
fn tls_config_error() {
let err = QuicError::TlsConfig("bad cert".to_string());
assert!(!err.is_cancelled());
assert!(!err.is_recoverable());
assert!(!err.is_connection_error());
}
#[test]
fn config_error() {
let err = QuicError::Config("invalid".to_string());
assert!(!err.is_cancelled());
assert!(!err.is_recoverable());
}
#[test]
fn display_cancelled() {
let err = QuicError::Cancelled;
assert_eq!(format!("{err}"), "cancelled");
}
#[test]
fn display_stream_closed() {
let err = QuicError::StreamClosed;
assert_eq!(format!("{err}"), "stream closed");
}
#[test]
fn display_endpoint_closed() {
let err = QuicError::EndpointClosed;
assert_eq!(format!("{err}"), "endpoint closed");
}
#[test]
fn display_open_stream() {
let err = QuicError::OpenStream;
assert_eq!(format!("{err}"), "failed to open stream");
}
#[test]
fn display_tls_config() {
let err = QuicError::TlsConfig("missing cert".to_string());
assert!(format!("{err}").contains("missing cert"));
}
#[test]
fn display_config() {
let err = QuicError::Config("bad value".to_string());
assert!(format!("{err}").contains("bad value"));
}
#[test]
fn display_io() {
let err = QuicError::Io(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"pipe broken",
));
assert!(format!("{err}").contains("pipe broken"));
}
#[test]
fn from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
let err = QuicError::Io(io_err);
assert!(matches!(err, QuicError::Io(_)));
}
#[test]
fn from_framework_cancel_error() {
let reason = crate::types::cancel::CancelReason::shutdown();
let err = crate::error::Error::cancelled(&reason);
let quic_err = QuicError::from(err);
assert!(quic_err.is_cancelled());
}
#[test]
fn from_framework_non_cancel_error() {
let err = crate::error::Error::new(crate::error::ErrorKind::RegionClosed);
let quic_err = QuicError::from(err);
assert!(matches!(quic_err, QuicError::Io(_)));
}
#[test]
fn debug_format() {
let err = QuicError::Cancelled;
let debug = format!("{err:?}");
assert!(debug.contains("Cancelled"));
}
}