axum_help/
lib.rs

1//! This crate make a series of enhancements for [Axum](axum)
2//!
3use axum::{http::StatusCode, response::IntoResponse, response::Response};
4use std::fmt::{Debug, Display};
5
6pub mod filter;
7
8/// The error type contains a [status code](StatusCode) and a string message.
9///
10/// It implements [IntoResponse], so can be used in [axum] handler.
11///
12/// # Example
13///
14/// ```
15/// # use axum::response::IntoResponse;
16/// # use axum_help::HttpError;
17/// #
18/// fn handler() -> Result<impl IntoResponse, HttpError> {
19///     Ok(())
20/// }
21/// ```
22/// Often it can to more convenient to use [HttpResult]
23///
24/// # Example
25/// ```
26/// # use axum::response::IntoResponse;
27/// # use axum_help::HttpResult;
28/// #
29/// fn handler() -> HttpResult<impl IntoResponse> {
30///     Ok(())
31/// }
32/// ```
33#[derive(PartialEq, Debug)]
34pub struct HttpError {
35    pub message: String,
36    pub status_code: StatusCode,
37}
38
39impl IntoResponse for HttpError {
40    fn into_response(self) -> Response {
41        let mut response = self.message.into_response();
42        *response.status_mut() = self.status_code;
43        response
44    }
45}
46
47impl<E> From<E> for HttpError
48where
49    E: Debug + Display + Sync + Send + 'static,
50{
51    fn from(e: E) -> Self {
52        Self {
53            message: format!("{:?}", e),
54            status_code: StatusCode::INTERNAL_SERVER_ERROR,
55        }
56    }
57}
58
59/// Construct an ad-hoc error from a string or existing error value.
60///
61/// This evaluates to an [HttpError]. It can take either just a string, or
62/// a format string with arguments. It also can take any custom type
63/// which implements Debug and Display.
64///
65/// If status code is not specified, [INTERNAL_SERVER_ERROR](StatusCode::INTERNAL_SERVER_ERROR)
66/// will be used.
67#[macro_export]
68macro_rules! http_err {
69    ($status: path, $fmt: literal, $($args: tt)+) => {
70        spa_rs::HttpError {
71            message: format!($fmt, $($args)+),
72            status_code: $status
73        }
74    };
75    ($status: path, $msg: literal) => {
76        spa_rs::HttpError {
77            message: $msg.to_string(),
78            status_code: $status
79        }
80    };
81    ($fmt: literal, $($args: tt)+) => {
82        http_err!(spa_rs::http::StatusCode::INTERNAL_SERVER_ERROR, $fmt, $($args)+)
83    };
84    ($msg: literal) => {
85        http_err!(spa_rs::http::StatusCode::INTERNAL_SERVER_ERROR, $msg)
86    };
87}
88
89/// Return early with an [`HttpError`]
90///
91/// This macro is equivalent to `return Err(`[`http_err!($args...)`][http_err!]`)`.
92///
93/// The surrounding function's or closure's return value is required to be
94/// Result<_, [`HttpError`]>
95///
96/// If status code is not specified, [INTERNAL_SERVER_ERROR](StatusCode::INTERNAL_SERVER_ERROR)
97/// will be used.
98///
99/// # Example
100///
101/// ```
102/// # use http::StatusCode;
103/// # use axum_help::{http_bail, HttpError, http_err, HttpResult};
104/// # use axum::response::IntoResponse;
105/// #
106/// fn get() -> HttpResult<()> {
107///     http_bail!(StatusCode::BAD_REQUEST, "Bad Request: {}", "some reason");
108/// }
109/// ```
110#[macro_export]
111macro_rules! http_bail {
112    ($($args: tt)+) => {
113        return Err(spa_rs::http_err!($($args)+));
114    };
115}
116
117/// Easily convert [std::result::Result] to [HttpResult]
118///
119/// # Example
120/// ```
121/// # use std::io::{Error, ErrorKind};
122/// # use axum_help::{HttpResult, HttpContext};
123/// # use http::StatusCode;
124/// #
125/// fn handler() -> HttpResult<()> {
126/// #   let result = Err(Error::new(ErrorKind::InvalidInput, "bad input"));
127///     result.http_context(StatusCode::BAD_REQUEST, "bad request")?;   
128/// #   let result = Err(Error::new(ErrorKind::InvalidInput, "bad input"));
129///     result.http_error("bad request")?;
130///
131///     Ok(())
132/// }
133/// ```
134pub trait HttpContext<T> {
135    fn http_context<C>(self, status_code: StatusCode, extra_msg: C) -> Result<T, HttpError>
136    where
137        C: Display + Send + Sync + 'static;
138
139    fn http_error<C>(self, extra_msg: C) -> Result<T, HttpError>
140    where
141        C: Display + Send + Sync + 'static;
142}
143
144impl<T, E> HttpContext<T> for Result<T, E>
145where
146    E: Debug + Sync + Send + 'static,
147{
148    fn http_context<C>(self, status_code: StatusCode, extra_msg: C) -> Result<T, HttpError>
149    where
150        C: Display + Send + Sync + 'static,
151    {
152        self.map_err(|e| HttpError {
153            message: format!("{}: {:?}", extra_msg, e),
154            status_code,
155        })
156    }
157
158    fn http_error<C>(self, extra_msg: C) -> Result<T, HttpError>
159    where
160        C: Display + Send + Sync + 'static,
161    {
162        self.map_err(|e| HttpError {
163            message: format!("{}: {:?}", extra_msg, e),
164            status_code: StatusCode::INTERNAL_SERVER_ERROR,
165        })
166    }
167}
168
169/// convenient return type when writing [axum] handler.
170///
171pub type HttpResult<T> = Result<T, HttpError>;
172
173#[cfg(test)]
174mod test {
175    use super::HttpError;
176    use axum::http::StatusCode;
177
178    #[test]
179    fn test_macros() -> Result<(), HttpError> {
180        let error = HttpError {
181            message: "aaa".to_string(),
182            status_code: StatusCode::INTERNAL_SERVER_ERROR,
183        };
184        assert_eq!(error, http_err!(StatusCode::INTERNAL_SERVER_ERROR, "aaa"));
185        assert_eq!(
186            error,
187            http_err!(StatusCode::INTERNAL_SERVER_ERROR, "{}aa", "a")
188        );
189        assert_eq!(error, http_err!("aaa"));
190        assert_eq!(error, http_err!("{}aa", "a"));
191        Ok(())
192    }
193}