use base_scheduler::{Cancelled, OperationError};
use std::{fmt, io};
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Cancelled,
Invalid(&'static str),
Limit(&'static str),
Truncated,
LengthMismatch,
Body(Box<dyn std::error::Error + Send + Sync>),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(_) => f.write_str("HTTP/1 connection I/O failed"),
Self::Cancelled => f.write_str("HTTP exchange cancelled"),
Self::Invalid(reason) => write!(f, "invalid HTTP/1 message: {reason}"),
Self::Limit(reason) => write!(f, "HTTP/1 limit exceeded: {reason}"),
Self::Truncated => f.write_str("truncated HTTP/1 message"),
Self::LengthMismatch => f.write_str("body does not match its declared length"),
Self::Body(_) => f.write_str("HTTP/1 body failed"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::Body(error) => Some(error.as_ref()),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Self::Io(error)
}
}
impl From<Cancelled> for Error {
fn from(_: Cancelled) -> Self {
Self::Cancelled
}
}
impl From<OperationError> for Error {
fn from(error: OperationError) -> Self {
match error {
OperationError::Cancelled(_) => Self::Cancelled,
OperationError::Io(error) => Self::Io(error),
}
}
}
impl From<base_http::FieldError> for Error {
fn from(error: base_http::FieldError) -> Self {
Self::Body(Box::new(error))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as _;
#[test]
fn wrapping_variants_describe_their_own_layer() {
let error = Error::Io(io::Error::from(io::ErrorKind::ConnectionRefused));
assert_eq!(error.to_string(), "HTTP/1 connection I/O failed");
assert_eq!(error.source().unwrap().to_string(), "connection refused");
let error = Error::from(base_http::FieldError::InvalidTrailer("host".to_owned()));
assert_eq!(error.to_string(), "HTTP/1 body failed");
assert!(error.source().is_some());
}
#[test]
fn leaf_variants_describe_the_failure_without_a_source() {
for (error, message) in [
(Error::Cancelled, "HTTP exchange cancelled"),
(
Error::Invalid("missing host"),
"invalid HTTP/1 message: missing host",
),
(
Error::Limit("message head"),
"HTTP/1 limit exceeded: message head",
),
(Error::Truncated, "truncated HTTP/1 message"),
(
Error::LengthMismatch,
"body does not match its declared length",
),
] {
assert_eq!(error.to_string(), message);
assert!(error.source().is_none());
}
}
}