http_kit/
macros.rs

1macro_rules! impl_error {
2    ($ty:ident,$message:expr) => {
3        #[doc = concat!("The error type of `", stringify!($ty), "`.")]
4        #[derive(Debug)]
5        pub struct $ty {
6            _priv: (),
7        }
8
9        impl $ty {
10            pub(crate) fn new() -> Self {
11                Self { _priv: () }
12            }
13        }
14
15        impl core::fmt::Display for $ty {
16            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
17                f.write_str($message)
18            }
19        }
20
21        impl core::error::Error for $ty {}
22    };
23}
24
25/// Defines a zero-sized type that implements [`HttpError`] with a custom formatter.
26///
27/// This macro is intended for library users who want lightweight marker error types
28/// that only carry a status code and a display representation.
29#[macro_export]
30macro_rules! http_error_fmt {
31    ($(#[$meta:meta])* $vis:vis $name:ident, $status:expr, |$ty_self:pat, $fmt:ident| $body:expr $(,)?) => {
32        $(#[$meta])*
33        #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
34        $vis struct $name {
35            _priv: (),
36        }
37
38        impl $name {
39            /// Creates a new instance of this error type.
40            pub const fn new() -> Self {
41                Self { _priv: () }
42            }
43        }
44
45        impl ::core::default::Default for $name {
46            fn default() -> Self {
47                Self::new()
48            }
49        }
50
51        impl ::core::fmt::Display for $name {
52            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
53                let $ty_self = self;
54                let $fmt = f;
55                $body
56            }
57        }
58
59        impl ::core::error::Error for $name {}
60
61        impl $crate::HttpError for $name {
62            fn status(&self) -> ::core::option::Option<$crate::StatusCode> {
63                ::core::option::Option::Some($status)
64            }
65        }
66    };
67}
68
69/// Defines a zero-sized [`HttpError`] type that renders as a static message.
70///
71/// # Examples
72///
73/// ```rust
74/// use http_kit::{http_error, StatusCode, HttpError};
75///
76/// http_error!(
77///     /// Reported when a resource is missing.
78///     pub NotFoundError,
79///     StatusCode::NOT_FOUND,
80///     "resource not found"
81/// );
82///
83/// let err = NotFoundError::new();
84/// assert_eq!(err.status(), Some(StatusCode::NOT_FOUND));
85/// assert_eq!(err.to_string(), "resource not found");
86/// ```
87#[macro_export]
88macro_rules! http_error {
89    ($(#[$meta:meta])* $vis:vis $name:ident, $status:expr, $message:expr $(,)?) => {
90        $crate::http_error_fmt!(
91            $(#[$meta])*
92            $vis $name,
93            $status,
94            |_, f| { f.write_str($message) },
95        );
96    };
97}
98
99#[cfg(test)]
100mod tests {
101    use crate::{HttpError, StatusCode};
102    use alloc::string::ToString;
103
104    http_error!(
105        /// Error returned when testing macros for missing resources.
106        pub MacroNotFound,
107        StatusCode::NOT_FOUND,
108        "macro missing"
109    );
110
111    http_error_fmt!(
112        /// Custom formatted macro error used in tests.
113        pub MacroDisplayError,
114        StatusCode::BAD_REQUEST,
115        |_, f| write!(f, "bad request (400)"),
116    );
117
118    #[test]
119    fn http_error_macros_create_expected_types() {
120        let not_found = MacroNotFound::new();
121        assert_eq!(not_found.status(), Some(StatusCode::NOT_FOUND));
122        assert_eq!(not_found.to_string(), "macro missing");
123
124        let display = MacroDisplayError::new();
125        assert_eq!(display.status(), Some(StatusCode::BAD_REQUEST));
126        assert_eq!(display.to_string(), "bad request (400)");
127    }
128}