use std::{error, fmt, io, io::Write, rc::Rc, string::FromUtf8Error};
use ntex_h2::{self as h2};
use ntex_http::{StatusCode, header};
pub use crate::channel::Canceled;
pub use ntex_http::error::Error as HttpError;
use crate::http::body::Body;
use crate::http::response::Response;
use crate::util::{BytesMut, Either, clone_io_error};
pub trait ResponseError: fmt::Display + fmt::Debug {
fn error_response(&self) -> Response {
let mut resp = Response::new(StatusCode::INTERNAL_SERVER_ERROR);
let mut buf = BytesMut::new();
let _ = write!(&mut buf, "{self}");
resp.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain; charset=utf-8"),
);
resp.set_body(Body::from(buf))
}
}
impl<T: ResponseError> ResponseError for &T {
fn error_response(&self) -> Response {
(*self).error_response()
}
}
impl<T: ResponseError> From<T> for Response {
fn from(err: T) -> Response {
let resp = err.error_response();
if resp.head().status == StatusCode::INTERNAL_SERVER_ERROR {
log::error!("Internal Server Error: {err:?}");
} else {
log::debug!("Error in response: {err:?}");
}
resp
}
}
impl ResponseError for HttpError {}
impl ResponseError for io::Error {}
impl ResponseError for serde_json::error::Error {}
#[derive(thiserror::Error, Debug)]
pub enum EncodeError {
#[error("Unsupported HTTP version specified, {0:?}")]
UnsupportedVersion(super::Version),
#[error("Unexpected end of bytes stream")]
UnexpectedEof,
#[error("Formater error {0}")]
Fmt(io::Error),
}
impl Clone for EncodeError {
fn clone(&self) -> Self {
match self {
EncodeError::UnexpectedEof => EncodeError::UnexpectedEof,
EncodeError::UnsupportedVersion(err) => EncodeError::UnsupportedVersion(*err),
EncodeError::Fmt(err) => EncodeError::Fmt(clone_io_error(err)),
}
}
}
#[derive(thiserror::Error, Copy, Clone, Debug)]
pub enum DecodeError {
#[error("Invalid Method specified")]
Method,
#[error("Uri error")]
Uri,
#[error("Invalid HTTP version specified")]
Version,
#[error("Invalid Header provided")]
Header,
#[error("Message head is too large")]
TooLarge(usize),
#[error("Message is incomplete")]
Incomplete,
#[error("Invalid Status provided")]
Status,
#[error("`InvalidInput` occurred while trying to parse incoming stream: {0}")]
InvalidInput(&'static str),
#[error("UTF8 error")]
Utf8,
}
impl From<ntex_http::compat::InvalidUri> for DecodeError {
fn from(_: ntex_http::compat::InvalidUri) -> DecodeError {
DecodeError::Uri
}
}
impl From<FromUtf8Error> for DecodeError {
fn from(_: FromUtf8Error) -> DecodeError {
DecodeError::Utf8
}
}
impl From<httparse::Error> for DecodeError {
fn from(err: httparse::Error) -> DecodeError {
match err {
httparse::Error::HeaderName
| httparse::Error::HeaderValue
| httparse::Error::NewLine
| httparse::Error::Token => DecodeError::Header,
httparse::Error::Status => DecodeError::Status,
httparse::Error::TooManyHeaders => DecodeError::TooLarge(0),
httparse::Error::Version => DecodeError::Version,
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum PayloadError {
#[error("A payload reached EOF, but is not complete. With error: {0:?}")]
Incomplete(Option<io::Error>),
#[error("Cannot decode content-encoding.")]
EncodingCorrupted,
#[error("A payload reached size limit.")]
Overflow,
#[error("A payload length is unknown.")]
UnknownLength,
#[error("{0}")]
Http2Payload(#[from] h2::StreamError),
#[error("Decode error: {0}")]
Decode(#[from] DecodeError),
#[error("{0}")]
Io(#[from] io::Error),
}
impl Clone for PayloadError {
fn clone(&self) -> PayloadError {
match self {
PayloadError::Incomplete(_) => PayloadError::Incomplete(None),
PayloadError::EncodingCorrupted => PayloadError::EncodingCorrupted,
PayloadError::Overflow => PayloadError::Overflow,
PayloadError::UnknownLength => PayloadError::UnknownLength,
PayloadError::Http2Payload(err) => PayloadError::Http2Payload(*err),
PayloadError::Decode(err) => PayloadError::Decode(*err),
PayloadError::Io(err) => PayloadError::Io(clone_io_error(err)),
}
}
}
impl From<Either<PayloadError, io::Error>> for PayloadError {
fn from(err: Either<PayloadError, io::Error>) -> Self {
match err {
Either::Left(err) => err,
Either::Right(err) => PayloadError::Io(err),
}
}
}
#[derive(thiserror::Error, Clone, Debug)]
pub enum DispatchError {
#[error("Service error")]
Service(Rc<dyn super::ResponseError>),
#[error("Control service error: {0}")]
Control(Rc<dyn std::error::Error>),
}
#[derive(thiserror::Error, Clone, Debug)]
pub enum H2Error {
#[error("Operation error: {0}")]
Operation(#[from] h2::OperationError),
#[error("Missing pseudo header: {0}")]
MissingPseudo(&'static str),
#[error("Uri")]
Uri,
#[error("{0}")]
Stream(#[from] Rc<dyn error::Error>),
}
impl From<ntex_http::compat::InvalidUri> for H2Error {
fn from(_: ntex_http::compat::InvalidUri) -> H2Error {
H2Error::Uri
}
}
#[derive(thiserror::Error, Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum ContentTypeError {
#[error("Cannot parse content type")]
ParseError,
#[error("Unknown content encoding")]
UnknownEncoding,
#[error("Unexpected Content-Type")]
Unexpected,
#[error("Content-Type is expected")]
Expected,
}
#[derive(thiserror::Error, Debug)]
pub enum BlockingError<E: fmt::Debug> {
#[error("{0:?}")]
Error(E),
#[error("Thread pool is gone")]
Canceled,
}
impl From<crate::rt::JoinError> for PayloadError {
fn from(_: crate::rt::JoinError) -> Self {
PayloadError::Io(io::Error::new(
io::ErrorKind::Interrupted,
"Operation is canceled",
))
}
}
impl From<crate::rt::BlockingError> for PayloadError {
fn from(_: crate::rt::BlockingError) -> Self {
PayloadError::Io(io::Error::new(
io::ErrorKind::Interrupted,
"Operation is canceled",
))
}
}
impl From<BlockingError<io::Error>> for PayloadError {
fn from(err: BlockingError<io::Error>) -> Self {
match err {
BlockingError::Error(e) => PayloadError::Io(e),
BlockingError::Canceled => PayloadError::Io(io::Error::new(
io::ErrorKind::Interrupted,
"Operation is canceled",
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ntex_http::Error as HttpError;
#[test]
fn test_into_response() {
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
let resp: Response = err.error_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_error_http_response() {
let orig = io::Error::other("other");
let resp: Response = orig.into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_payload_error() {
let err: PayloadError = io::Error::other("DecodeError").into();
assert!(format!("{err}").contains("DecodeError"));
let err: PayloadError = BlockingError::Canceled.into();
assert!(format!("{err}").contains("Operation is canceled"));
let err: PayloadError =
BlockingError::Error(io::Error::other("DecodeError")).into();
assert!(format!("{err}").contains("DecodeError"));
let err = PayloadError::Incomplete(None);
assert_eq!(
format!("{err}"),
"A payload reached EOF, but is not complete. With error: None"
);
}
macro_rules! from {
($from:expr => $error:pat) => {
match DecodeError::from($from) {
e @ $error => {
assert!(format!("{e}").len() >= 5);
}
e => unreachable!("{e:?}"),
}
};
}
#[test]
fn test_from() {
from!(httparse::Error::HeaderName => DecodeError::Header);
from!(httparse::Error::HeaderName => DecodeError::Header);
from!(httparse::Error::HeaderValue => DecodeError::Header);
from!(httparse::Error::NewLine => DecodeError::Header);
from!(httparse::Error::Status => DecodeError::Status);
from!(httparse::Error::Token => DecodeError::Header);
from!(httparse::Error::TooManyHeaders => DecodeError::TooLarge(0));
from!(httparse::Error::Version => DecodeError::Version);
}
}