1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Error utilities for axum.

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};

/// Error that can be used as axum response, with an appropriate HTTP status code and – except for
/// `Internal` – with one or more error messages conveyed as a JSON string array.
pub enum Error {
    /// `400 Bad Request`, e.g. because of invalid path or query arguments.
    InvalidArgs(Vec<String>),

    /// `404 Not Found`.
    NotFound(String),

    /// `409 Conflict`, e.g. because of an already existing resource.
    Conflict(String),

    /// `422 Unprocessable Entity`, e.g. because of the JSON payload could not be parsed.
    InvalidEntity(Vec<String>),

    /// `500 Internal Server Error`.
    Internal,
}

impl Error {
    /// Create [Error::InvalidArgs] with the given error.
    pub fn invalid_args<T>(error: T) -> Self
    where
        T: ToString,
    {
        let errors = vec![error.to_string()];
        Error::InvalidArgs(errors)
    }

    /// Create [Error::InvalidArgs] with the given errors.
    pub fn invalid_args_all<I, T>(errors: I) -> Self
    where
        I: IntoIterator<Item = T>,
        T: ToString,
    {
        let errors = errors.into_iter().map(|e| e.to_string()).collect();
        Error::InvalidArgs(errors)
    }

    /// Create [Error::NotFound] with the given error.
    pub fn not_found<T>(error: T) -> Self
    where
        T: ToString,
    {
        Error::NotFound(error.to_string())
    }

    /// Create [Error::Conflict] with the given error.
    pub fn conflict<T>(error: T) -> Self
    where
        T: ToString,
    {
        Error::Conflict(error.to_string())
    }

    /// Create [Error::InvalidEntity] with the given error.
    pub fn invalid_entity<T>(error: T) -> Self
    where
        T: ToString,
    {
        let errors = vec![error.to_string()];
        Error::InvalidEntity(errors)
    }

    /// Create [Error::InvalidEntity] with the given errors.
    pub fn invalid_entity_all<I, T>(errors: I) -> Self
    where
        I: IntoIterator<Item = T>,
        T: ToString,
    {
        let errors = errors.into_iter().map(|e| e.to_string()).collect();
        Error::InvalidEntity(errors)
    }
}

impl IntoResponse for Error {
    fn into_response(self) -> Response {
        match self {
            Error::InvalidArgs(errors) => {
                let errors = Json(
                    errors
                        .into_iter()
                        .map(|e| e.to_string())
                        .collect::<Vec<_>>(),
                );
                (StatusCode::BAD_REQUEST, errors).into_response()
            }

            Error::NotFound(error) => {
                let errors = Json(vec![error.to_string()]);
                (StatusCode::NOT_FOUND, errors).into_response()
            }

            Error::Conflict(error) => {
                let errors = Json(vec![error.to_string()]);
                (StatusCode::CONFLICT, errors).into_response()
            }

            Error::InvalidEntity(errors) => {
                let errors = Json(
                    errors
                        .into_iter()
                        .map(|e| e.to_string())
                        .collect::<Vec<_>>(),
                );
                (StatusCode::UNPROCESSABLE_ENTITY, errors).into_response()
            }

            Error::Internal => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::anyhow;
    use std::iter;
    use thiserror::Error;

    #[derive(Debug, Error)]
    #[error("test")]
    struct TestError;

    #[test]
    fn test_invalid_args() {
        Error::invalid_args("test").into_response();
        Error::invalid_args(anyhow!("test")).into_response();
        let response = Error::invalid_args(TestError).into_response();
        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    }

    #[test]
    fn test_invalid_args_all() {
        Error::invalid_args_all(vec!["test"]).into_response();
        let response = Error::invalid_args_all(iter::once(TestError)).into_response();
        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    }

    #[test]
    fn test_not_found() {
        Error::not_found("test").into_response();
        Error::not_found(anyhow!("test")).into_response();
        let response = Error::not_found(TestError).into_response();
        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[test]
    fn test_conflict() {
        Error::conflict("test").into_response();
        Error::conflict(anyhow!("test")).into_response();
        let response = Error::conflict(TestError).into_response();
        assert_eq!(response.status(), StatusCode::CONFLICT);
    }

    #[test]
    fn test_invalid_entity() {
        Error::invalid_entity("test").into_response();
        Error::invalid_entity(anyhow!("test")).into_response();
        let response = Error::invalid_entity(TestError).into_response();
        assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
    }

    #[test]
    fn test_invalid_entity_all() {
        Error::invalid_entity_all(vec!["test"]).into_response();
        let response = Error::invalid_entity_all(iter::once(TestError)).into_response();
        assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
    }

    #[test]
    fn test_internal() {
        let response = Error::Internal.into_response();
        assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }
}