dhttp/core/
error.rs

1use std::convert::Infallible;
2use std::io::{self, ErrorKind};
3use std::error::Error;
4
5use crate::reqres::StatusCode;
6
7/// How should this error be handled
8///
9/// Returned from [`HttpError::error_type`]
10#[derive(Debug, Clone, Copy)]
11pub enum HttpErrorType {
12    /// Terminates the connection (examples: network I/O error)
13    Fatal,
14    /// Status code (examples: 404 Not found, 403 Forbidden)
15    Hidden,
16    /// Error with detailed description (could not parse json, wrong file type, etc)
17    User,
18}
19
20/// Error trait for any service error
21/// # Example implementation
22/// `impl HttpError for MyError {}`
23pub trait HttpError: Error + Send + 'static {
24    /// Name of this error (type name by default)
25    fn name(&self) -> &'static str {
26        // everything after last ::
27        std::any::type_name::<Self>().split("::").last().unwrap()
28    }
29
30    /// How should this error be handled, check [`HttpErrorType`] for more info (`User` by default)
31    fn error_type(&self) -> HttpErrorType {
32        HttpErrorType::User
33    }
34
35    /// Provides HTTP-friendly description of this error (`.to_string()` by default)
36    fn http_description(&self) -> String {
37        self.to_string()
38    }
39
40    /// Which status code should be used for this error
41    fn status_code(&self) -> StatusCode {
42        StatusCode::INTERNAL_SERVER_ERROR
43    }
44}
45
46impl<E: HttpError> From<E> for Box<dyn HttpError> {
47    fn from(value: E) -> Box<dyn HttpError> {
48        Box::new(value)
49    }
50}
51
52fn is_net(kind: ErrorKind) -> bool {
53    matches!(kind, ErrorKind::ConnectionRefused
54    | ErrorKind::ConnectionReset
55    | ErrorKind::HostUnreachable
56    | ErrorKind::NetworkUnreachable
57    | ErrorKind::ConnectionAborted
58    | ErrorKind::NotConnected
59    | ErrorKind::AddrInUse
60    | ErrorKind::AddrNotAvailable
61    | ErrorKind::NetworkDown
62    | ErrorKind::BrokenPipe
63    | ErrorKind::TimedOut)
64}
65
66impl HttpError for io::Error {
67    fn name(&self) -> &'static str {
68        "io::Error"
69    }
70
71    fn error_type(&self) -> HttpErrorType {
72        if is_net(self.kind()) {
73            HttpErrorType::Fatal
74        } else {
75            HttpErrorType::User
76        }
77    }
78
79    fn http_description(&self) -> String {
80        match self.kind() {
81            ErrorKind::NotFound | ErrorKind::NotADirectory => "The requested resource was not found on this server".to_string(),
82            ErrorKind::PermissionDenied => "Access denied".to_string(),
83            _ => self.to_string(),
84        }
85    }
86
87    fn status_code(&self) -> StatusCode {
88        match self.kind() {
89            ErrorKind::NotFound | ErrorKind::NotADirectory => StatusCode::NOT_FOUND,
90            ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
91            _ => StatusCode::INTERNAL_SERVER_ERROR,
92        }
93    }
94}
95
96impl HttpError for Infallible {}
97
98impl HttpError for tokio::task::JoinError {
99    fn error_type(&self) -> HttpErrorType {
100        // This is a panic message, should not be displayed
101        HttpErrorType::Hidden
102    }
103}
104
105impl HttpError for std::string::FromUtf8Error {}