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 /// Set the HTTP status code for this error.
63 pub fn set_status(mut self, status: StatusCode) -> Self {
64 self.status = status;
65 self
66 }
67}
68
69impl fmt::Display for Error {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "{}", self.inner)
72 }
73}
74
75/// Trait for errors that have an associated HTTP status code.
76///
77/// This trait extends the standard `Error` trait to include a method for retrieving
78/// the HTTP status code associated with the error.
79pub trait HttpError: core::error::Error + Send + Sync + 'static {
80 /// Returns the HTTP status code associated with this error.
81 ///
82 /// # Examples
83 ///
84 /// ```rust
85 /// use http_kit::{HttpError,StatusCode};
86 /// use thiserror::Error;
87 ///
88 /// #[derive(Debug, Error)]
89 /// #[error("My error occurred")]
90 /// struct MyError;
91 ///
92 /// impl HttpError for MyError {
93 /// fn status(&self) -> StatusCode {
94 /// StatusCode::INTERNAL_SERVER_ERROR
95 /// }
96 /// }
97 /// let err = MyError;
98 /// assert_eq!(err.status(), StatusCode::INTERNAL_SERVER_ERROR);
99 /// assert_eq!(err.to_string(), "My error occurred");
100 /// ```
101 ///
102 /// Alternatively, you can use the [`http_error!`](crate::http_error!) macro to build
103 /// zero-sized types that already implement `HttpError` with a fixed status code:
104 ///
105 /// ```rust
106 /// use http_kit::{http_error, StatusCode, HttpError};
107 ///
108 /// http_error!(pub BadGateway, StatusCode::BAD_GATEWAY, "upstream failed");
109 /// let err = BadGateway::new();
110 /// assert_eq!(err.status(), StatusCode::BAD_GATEWAY);
111 /// ```
112 fn status(&self) -> StatusCode {
113 StatusCode::INTERNAL_SERVER_ERROR
114 }
115}
116
117/// A specialized Result type for HTTP operations.
118pub type Result<T, E = Error> = core::result::Result<T, E>;
119
120/// Extension trait for adding status codes to Results.
121pub trait ResultExt<T> {
122 /// Map the error variant to an [`Error`] with the given status code.
123 fn status(self, status: StatusCode) -> Result<T, Error>;
124}
125
126impl<T, E> ResultExt<T> for core::result::Result<T, E>
127where
128 E: Into<eyre::Report>,
129{
130 fn status(self, status: StatusCode) -> Result<T, Error> {
131 self.map_err(|e| Error::new(e).set_status(status))
132 }
133}
134
135impl<T> ResultExt<T> for core::option::Option<T> {
136 fn status(self, status: StatusCode) -> Result<T, Error> {
137 self.ok_or_else(|| Error::msg("None value").set_status(status))
138 }
139}
140
141/// A boxed HTTP error trait object.
142///
143/// > Unlike `Box<dyn std::error::Error>`, this type carries HTTP status code information, and implements the `HttpError` trait.
144pub type BoxHttpError = Box<dyn HttpError>;
145
146impl<E> From<E> for Error
147where
148 E: Into<eyre::Report>,
149{
150 fn from(e: E) -> Self {
151 Error::new(e)
152 }
153}
154
155impl core::error::Error for BoxHttpError {}
156impl HttpError for BoxHttpError {
157 fn status(&self) -> StatusCode {
158 (**self).status()
159 }
160}
161
162impl HttpError for Infallible {
163 fn status(&self) -> StatusCode {
164 unreachable!("Infallible can never be instantiated")
165 }
166}