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}