axum_handler_error/
lib.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6
7/// Error that can be used as axum response, with an appropriate HTTP status code and – except for
8/// `Internal` – with one or more error messages conveyed as a JSON string array.
9pub enum Error {
10    /// `400 Bad Request`, e.g. because of invalid path or query arguments.
11    InvalidArgs(Vec<String>),
12
13    /// `404 Not Found`.
14    NotFound(String),
15
16    /// `409 Conflict`, e.g. because of an already existing resource.
17    Conflict(String),
18
19    /// `422 Unprocessable Entity`, e.g. because of the JSON payload could not be parsed.
20    InvalidEntity(Vec<String>),
21
22    /// `500 Internal Server Error`.
23    Internal,
24}
25
26impl Error {
27    /// Create [Error::InvalidArgs] with the given error.
28    pub fn invalid_args<T>(error: T) -> Self
29    where
30        T: ToString,
31    {
32        let errors = vec![error.to_string()];
33        Error::InvalidArgs(errors)
34    }
35
36    /// Create [Error::InvalidArgs] with the given errors.
37    pub fn invalid_args_all<I, T>(errors: I) -> Self
38    where
39        I: IntoIterator<Item = T>,
40        T: ToString,
41    {
42        let errors = errors.into_iter().map(|e| e.to_string()).collect();
43        Error::InvalidArgs(errors)
44    }
45
46    /// Create [Error::NotFound] with the given error.
47    pub fn not_found<T>(error: T) -> Self
48    where
49        T: ToString,
50    {
51        Error::NotFound(error.to_string())
52    }
53
54    /// Create [Error::Conflict] with the given error.
55    pub fn conflict<T>(error: T) -> Self
56    where
57        T: ToString,
58    {
59        Error::Conflict(error.to_string())
60    }
61
62    /// Create [Error::InvalidEntity] with the given error.
63    pub fn invalid_entity<T>(error: T) -> Self
64    where
65        T: ToString,
66    {
67        let errors = vec![error.to_string()];
68        Error::InvalidEntity(errors)
69    }
70
71    /// Create [Error::InvalidEntity] with the given errors.
72    pub fn invalid_entity_all<I, T>(errors: I) -> Self
73    where
74        I: IntoIterator<Item = T>,
75        T: ToString,
76    {
77        let errors = errors.into_iter().map(|e| e.to_string()).collect();
78        Error::InvalidEntity(errors)
79    }
80}
81
82impl IntoResponse for Error {
83    fn into_response(self) -> Response {
84        match self {
85            Error::InvalidArgs(errors) => {
86                let errors = Json(
87                    errors
88                        .into_iter()
89                        .map(|e| e.to_string())
90                        .collect::<Vec<_>>(),
91                );
92                (StatusCode::BAD_REQUEST, errors).into_response()
93            }
94
95            Error::NotFound(error) => {
96                let errors = Json(vec![error.to_string()]);
97                (StatusCode::NOT_FOUND, errors).into_response()
98            }
99
100            Error::Conflict(error) => {
101                let errors = Json(vec![error.to_string()]);
102                (StatusCode::CONFLICT, errors).into_response()
103            }
104
105            Error::InvalidEntity(errors) => {
106                let errors = Json(
107                    errors
108                        .into_iter()
109                        .map(|e| e.to_string())
110                        .collect::<Vec<_>>(),
111                );
112                (StatusCode::UNPROCESSABLE_ENTITY, errors).into_response()
113            }
114
115            Error::Internal => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use anyhow::anyhow;
124    use std::iter;
125    use thiserror::Error;
126
127    #[derive(Debug, Error)]
128    #[error("test")]
129    struct TestError;
130
131    #[test]
132    fn test_invalid_args() {
133        Error::invalid_args("test").into_response();
134        Error::invalid_args(anyhow!("test")).into_response();
135        let response = Error::invalid_args(TestError).into_response();
136        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
137    }
138
139    #[test]
140    fn test_invalid_args_all() {
141        Error::invalid_args_all(vec!["test"]).into_response();
142        let response = Error::invalid_args_all(iter::once(TestError)).into_response();
143        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
144    }
145
146    #[test]
147    fn test_not_found() {
148        Error::not_found("test").into_response();
149        Error::not_found(anyhow!("test")).into_response();
150        let response = Error::not_found(TestError).into_response();
151        assert_eq!(response.status(), StatusCode::NOT_FOUND);
152    }
153
154    #[test]
155    fn test_conflict() {
156        Error::conflict("test").into_response();
157        Error::conflict(anyhow!("test")).into_response();
158        let response = Error::conflict(TestError).into_response();
159        assert_eq!(response.status(), StatusCode::CONFLICT);
160    }
161
162    #[test]
163    fn test_invalid_entity() {
164        Error::invalid_entity("test").into_response();
165        Error::invalid_entity(anyhow!("test")).into_response();
166        let response = Error::invalid_entity(TestError).into_response();
167        assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
168    }
169
170    #[test]
171    fn test_invalid_entity_all() {
172        Error::invalid_entity_all(vec!["test"]).into_response();
173        let response = Error::invalid_entity_all(iter::once(TestError)).into_response();
174        assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
175    }
176
177    #[test]
178    fn test_internal() {
179        let response = Error::Internal.into_response();
180        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
181    }
182}