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 alloc::string::String;
27use core::convert::Infallible;
28use core::fmt;
29use http::StatusCode;
30
31/// A concrete error type for HTTP operations.
32#[derive(Debug)]
33pub struct Error {
34    inner: Box<dyn core::error::Error + Send + Sync>,
35    status: Option<StatusCode>,
36}
37
38impl Error {
39    /// Create a new error from a message.
40    pub fn msg(msg: impl Into<String>) -> Self {
41        Self {
42            inner: msg.into().into(),
43            status: None,
44        }
45    }
46
47    /// Create a new error from any standard error type.
48    pub fn new(e: impl Into<Box<dyn core::error::Error + Send + Sync>>) -> Self {
49        Self {
50            inner: e.into(),
51            status: None,
52        }
53    }
54
55    /// Set the HTTP status code for this error.
56    pub fn set_status(mut self, status: StatusCode) -> Self {
57        self.status = Some(status);
58        self
59    }
60}
61
62impl fmt::Display for Error {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}", self.inner)
65    }
66}
67
68impl core::error::Error for Error {
69    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
70        Some(self.inner.as_ref())
71    }
72}
73
74/// Trait for errors that have an associated HTTP status code.
75///
76/// This trait extends the standard `Error` trait to include a method for retrieving
77/// the HTTP status code associated with the error.
78pub trait HttpError: core::error::Error + Send + Sync + 'static {
79    /// Returns the HTTP status code associated with this error.
80    ///
81    /// # Examples
82    ///
83    /// ```rust
84    /// use http_kit::{HttpError,StatusCode};
85    /// use thiserror::Error;
86    ///
87    /// #[derive(Debug, Error)]
88    /// #[error("My error occurred")]
89    /// struct MyError;
90    ///
91    /// impl HttpError for MyError {
92    ///     fn status(&self) -> Option<StatusCode> {
93    ///         Some(StatusCode::INTERNAL_SERVER_ERROR)
94    ///     }
95    /// }
96    /// let err = MyError;
97    /// assert_eq!(err.status(), Some(StatusCode::INTERNAL_SERVER_ERROR));
98    /// assert_eq!(err.to_string(), "My error occurred");
99    /// ```
100    ///
101    /// Alternatively, you can use the [`http_error!`](crate::http_error!) macro to build
102    /// zero-sized types that already implement `HttpError` with a fixed status code:
103    ///
104    /// ```rust
105    /// use http_kit::{http_error, StatusCode, HttpError};
106    ///
107    /// http_error!(pub BadGateway, StatusCode::BAD_GATEWAY, "upstream failed");
108    /// let err = BadGateway::new();
109    /// assert_eq!(err.status(), Some(StatusCode::BAD_GATEWAY));
110    /// ```
111    fn status(&self) -> Option<StatusCode>;
112
113    /// If the remote serve responded with an http status code instead of a connection error, it would be true.
114    fn is_remote(&self) -> bool {
115        self.status().is_some()
116    }
117}
118
119impl HttpError for Error {
120    fn status(&self) -> Option<StatusCode> {
121        self.status
122    }
123}
124
125/// A specialized Result type for HTTP operations.
126pub type Result<T, E = Error> = core::result::Result<T, E>;
127
128/// Extension trait for adding status codes to Results.
129pub trait ResultExt<T> {
130    /// Map the error variant to an [`Error`] with the given status code.
131    fn status(self, status: StatusCode) -> Result<T, Error>;
132}
133
134impl<T, E> ResultExt<T> for core::result::Result<T, E>
135where
136    E: Into<Box<dyn core::error::Error + Send + Sync>>,
137{
138    fn status(self, status: StatusCode) -> Result<T, Error> {
139        self.map_err(|e| Error::new(e).set_status(status))
140    }
141}
142
143/// A boxed HTTP error trait object.
144///
145/// > Unlike `Box<dyn std::error::Error>`, this type carries HTTP status code information, and implements the `HttpError` trait.
146pub type BoxHttpError = Box<dyn HttpError>;
147
148impl From<crate::BodyError> for Error {
149    fn from(e: crate::BodyError) -> Self {
150        Error::new(e)
151    }
152}
153
154#[cfg(feature = "json")]
155impl From<serde_json::Error> for Error {
156    fn from(e: serde_json::Error) -> Self {
157        Error::new(e)
158    }
159}
160
161impl core::error::Error for BoxHttpError {}
162impl HttpError for BoxHttpError {
163    fn status(&self) -> Option<StatusCode> {
164        (**self).status()
165    }
166}
167
168impl HttpError for Infallible {
169    fn status(&self) -> Option<StatusCode> {
170        unreachable!("Infallible can never be instantiated")
171    }
172}