1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum Error {
8 #[error("HTTP error: {0}")]
9 Http(String),
10
11 #[error("Status {0}: {1}")]
12 Status(u16, String),
13
14 #[error("Unauthorized: {0}")]
15 Unauthorized(String),
16
17 #[error("Forbidden: {0}")]
18 Forbidden(String),
19
20 #[error("DI error: {0}")]
21 Di(String),
22
23 #[error("Routing error: {0}")]
24 Routing(String),
25
26 #[error("Serialization error: {0}")]
27 Serialization(#[from] serde_json::Error),
28
29 #[error("response serialization failed: {0}")]
38 ResponseSerialization(#[source] serde_json::Error),
39
40 #[error("{0}")]
43 State(String),
44
45 #[error("Internal error: {0}")]
46 Internal(String),
47
48 #[error("{0}")]
49 Message(String),
50
51 #[error("{0}")]
53 Validation(String),
54
55 #[error("{0}")]
57 NotFound(String),
58
59 #[error("{0}")]
61 Conflict(String),
62
63 #[error("{0}")]
65 PayloadTooLarge(String),
66
67 #[error("{0}")]
69 UnsupportedMediaType(String),
70
71 #[error("request problem details")]
76 Problem(Box<crate::problem::ProblemDetails>),
77}
78
79impl Error {
80 pub fn status_code(&self) -> u16 {
85 match self {
86 Error::Http(_) => 400,
87 Error::Status(code, _) => *code,
88 Error::Unauthorized(_) => 401,
89 Error::Forbidden(_) => 403,
90 Error::Di(_) => 500,
91 Error::Routing(_) => 404,
92 Error::Serialization(_) => 400,
93 Error::ResponseSerialization(_) => 500,
94 Error::State(_) => 500,
95 Error::Internal(_) => 500,
96 Error::Message(_) => 500,
97 Error::Validation(_) => 400,
98 Error::NotFound(_) => 404,
99 Error::Conflict(_) => 409,
100 Error::PayloadTooLarge(_) => 413,
101 Error::UnsupportedMediaType(_) => 415,
102 Error::Problem(problem) => problem.status,
103 }
104 }
105
106 pub fn public_detail(&self) -> Option<&str> {
114 match self {
115 Error::Http(detail)
116 | Error::Unauthorized(detail)
117 | Error::Forbidden(detail)
118 | Error::Routing(detail)
119 | Error::Validation(detail)
120 | Error::NotFound(detail)
121 | Error::Conflict(detail)
122 | Error::PayloadTooLarge(detail)
123 | Error::UnsupportedMediaType(detail) => Some(detail),
124 Error::Status(_, detail) => Some(detail),
125 Error::Di(_)
128 | Error::Serialization(_)
129 | Error::ResponseSerialization(_)
130 | Error::State(_)
131 | Error::Internal(_)
132 | Error::Message(_)
133 | Error::Problem(_) => None,
134 }
135 }
136}
137
138pub type Result<T> = std::result::Result<T, Error>;
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 fn serde_error() -> serde_json::Error {
147 serde_json::from_str::<serde_json::Value>("{").unwrap_err()
148 }
149
150 #[test]
151 fn response_serialization_maps_to_500_and_hides_detail() {
152 let err = Error::ResponseSerialization(serde_error());
153 assert_eq!(err.status_code(), 500);
154 assert_eq!(err.public_detail(), None);
155 assert!(err
156 .to_string()
157 .starts_with("response serialization failed:"));
158 }
159
160 #[test]
161 fn state_error_maps_to_500_and_hides_detail() {
162 let err = Error::State("response body already written".into());
163 assert_eq!(err.status_code(), 500);
164 assert_eq!(err.public_detail(), None);
165 assert_eq!(err.to_string(), "response body already written");
166 }
167
168 #[test]
169 fn request_serialization_stays_a_client_error() {
170 let err: Error = serde_error().into();
173 assert!(matches!(err, Error::Serialization(_)));
174 assert_eq!(err.status_code(), 400);
175 assert_eq!(err.public_detail(), None);
176 }
177}