http_kit/
error.rs

1//! Error types and utilities.
2//!
3//! This module provides the core error handling infrastructure. The main types are:
4//!
5//! - [`Error`] - The main error type used throughout HTTP operations
6//! - [`Result`] - A specialized Result type alias for HTTP operations
7//! - [`ResultExt`] - Extension trait that adds HTTP status code handling
8//!
9//! The error types integrate with standard Rust error handling while adding HTTP-specific
10//! functionality like status codes.
11//!
12//! # Examples
13//!
14//! ```rust
15//! use http_kit::{Error, Result, ResultExt};
16//! use http::StatusCode;
17//!
18//! // Create an error with a status code
19//! let err = Error::msg("not found").set_status(StatusCode::NOT_FOUND);
20//!
21//! // Add status code to existing Result
22//! let result: Result<()> = Ok::<(), std::convert::Infallible>(()).status(StatusCode::OK);
23//! ```
24//!
25use alloc::boxed::Box;
26use core::convert::Infallible;
27use core::fmt::{self, Debug, Display};
28use http::StatusCode;
29
30/// A concrete error type for HTTP operations.
31///
32/// Note that this type doesn't implement `HttpError` directly, but also provide `status` method
33/// to get the associated status code.
34#[derive(Debug)]
35pub struct Error {
36    inner: eyre::Report,
37    status: StatusCode,
38}
39
40impl Error {
41    /// Create a new error with a custom message.
42    pub fn msg(msg: impl Display + Send + Sync + Debug + 'static) -> Self {
43        Self {
44            inner: eyre::Report::msg(msg),
45            status: StatusCode::INTERNAL_SERVER_ERROR,
46        }
47    }
48
49    /// Create a new error from any standard error type.
50    pub fn new(e: impl Into<eyre::Report>) -> Self {
51        Self {
52            inner: e.into(),
53            status: StatusCode::INTERNAL_SERVER_ERROR,
54        }
55    }
56
57    /// Consume the error and return the inner `eyre::Report`.
58    pub fn into_inner(self) -> eyre::Report {
59        self.inner
60    }
61
62    /// Convert this error into a boxed HTTP error trait object.
63    pub fn into_boxed_http_error(self) -> BoxHttpError {
64        struct Wrapper(Error);
65        impl Debug for Wrapper {
66            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67                Debug::fmt(&self.0, f)
68            }
69        }
70        impl Display for Wrapper {
71            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72                Display::fmt(&self.0, f)
73            }
74        }
75        impl core::error::Error for Wrapper {
76            fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
77                self.0.inner.source()
78            }
79        }
80        impl HttpError for Wrapper {
81            fn status(&self) -> StatusCode {
82                self.0.status
83            }
84        }
85        Box::new(Wrapper(self))
86    }
87
88    /// Set the HTTP status code for this error.
89    pub fn set_status(mut self, status: StatusCode) -> Self {
90        self.status = status;
91        self
92    }
93}
94
95impl fmt::Display for Error {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}", self.inner)
98    }
99}
100
101/// Trait for errors that have an associated HTTP status code.
102///
103/// This trait extends the standard `Error` trait to include a method for retrieving
104/// the HTTP status code associated with the error.
105pub trait HttpError: core::error::Error + Send + Sync + 'static {
106    /// Returns the HTTP status code associated with this error.
107    ///
108    /// # Examples
109    ///
110    /// ```rust
111    /// use http_kit::{HttpError,StatusCode};
112    /// use thiserror::Error;
113    ///
114    /// #[derive(Debug, Error)]
115    /// #[error("My error occurred")]
116    /// struct MyError;
117    ///
118    /// impl HttpError for MyError {
119    ///     fn status(&self) -> StatusCode {
120    ///         StatusCode::INTERNAL_SERVER_ERROR
121    ///     }
122    /// }
123    /// let err = MyError;
124    /// assert_eq!(err.status(), StatusCode::INTERNAL_SERVER_ERROR);
125    /// assert_eq!(err.to_string(), "My error occurred");
126    /// ```
127    ///
128    /// Alternatively, you can use the [`http_error!`](crate::http_error!) macro to build
129    /// zero-sized types that already implement `HttpError` with a fixed status code:
130    ///
131    /// ```rust
132    /// use http_kit::{http_error, StatusCode, HttpError};
133    ///
134    /// http_error!(pub BadGateway, StatusCode::BAD_GATEWAY, "upstream failed");
135    /// let err = BadGateway::new();
136    /// assert_eq!(err.status(), StatusCode::BAD_GATEWAY);
137    /// ```
138    fn status(&self) -> StatusCode {
139        StatusCode::INTERNAL_SERVER_ERROR
140    }
141}
142
143/// A specialized Result type for HTTP operations.
144pub type Result<T, E = Error> = core::result::Result<T, E>;
145
146/// Extension trait for adding status codes to Results.
147pub trait ResultExt<T> {
148    /// Map the error variant to an [`Error`] with the given status code.
149    fn status(self, status: StatusCode) -> Result<T, Error>;
150}
151
152impl<T, E> ResultExt<T> for core::result::Result<T, E>
153where
154    E: Into<eyre::Report>,
155{
156    fn status(self, status: StatusCode) -> Result<T, Error> {
157        self.map_err(|e| Error::new(e).set_status(status))
158    }
159}
160
161impl<T> ResultExt<T> for core::option::Option<T> {
162    fn status(self, status: StatusCode) -> Result<T, Error> {
163        self.ok_or_else(|| Error::msg("None value").set_status(status))
164    }
165}
166
167/// A boxed HTTP error trait object.
168///
169/// > Unlike `Box<dyn std::error::Error>`, this type carries HTTP status code information, and implements the `HttpError` trait.
170pub type BoxHttpError = Box<dyn HttpError>;
171
172impl<E> From<E> for Error
173where
174    E: Into<eyre::Report>,
175{
176    fn from(e: E) -> Self {
177        Error::new(e)
178    }
179}
180
181impl core::error::Error for BoxHttpError {}
182impl HttpError for BoxHttpError {
183    fn status(&self) -> StatusCode {
184        (**self).status()
185    }
186}
187
188impl HttpError for Infallible {
189    fn status(&self) -> StatusCode {
190        unreachable!("Infallible can never be instantiated")
191    }
192}