1use axum_core::response::IntoResponse;
2use http::StatusCode;
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct HttpError {
8 pub status: StatusCode,
9 pub message: Option<String>,
10}
11
12impl HttpError {
13 pub fn new<M: Into<String>>(status: StatusCode, message: M) -> Self {
14 HttpError {
15 status,
16 message: Some(message.into()),
17 }
18 }
19
20 pub fn err<T>(status: StatusCode, message: impl Into<String>) -> Result<T, Self> {
21 Err(HttpError::new(status, message))
22 }
23
24 pub fn bad_request<T>(message: impl Into<String>) -> Result<T, Self> {
26 Self::err(StatusCode::BAD_REQUEST, message)
27 }
28 pub fn unauthorized<T>(message: impl Into<String>) -> Result<T, Self> {
29 Self::err(StatusCode::UNAUTHORIZED, message)
30 }
31 pub fn payment_required<T>(message: impl Into<String>) -> Result<T, Self> {
32 Self::err(StatusCode::PAYMENT_REQUIRED, message)
33 }
34 pub fn forbidden<T>(message: impl Into<String>) -> Result<T, Self> {
35 Self::err(StatusCode::FORBIDDEN, message)
36 }
37 pub fn not_found<T>(message: impl Into<String>) -> Result<T, Self> {
38 Self::err(StatusCode::NOT_FOUND, message)
39 }
40 pub fn method_not_allowed<T>(message: impl Into<String>) -> Result<T, Self> {
41 Self::err(StatusCode::METHOD_NOT_ALLOWED, message)
42 }
43 pub fn not_acceptable<T>(message: impl Into<String>) -> Result<T, Self> {
44 Self::err(StatusCode::NOT_ACCEPTABLE, message)
45 }
46 pub fn proxy_auth_required<T>(message: impl Into<String>) -> Result<T, Self> {
47 Self::err(StatusCode::PROXY_AUTHENTICATION_REQUIRED, message)
48 }
49 pub fn request_timeout<T>(message: impl Into<String>) -> Result<T, Self> {
50 Self::err(StatusCode::REQUEST_TIMEOUT, message)
51 }
52 pub fn conflict<T>(message: impl Into<String>) -> Result<T, Self> {
53 Self::err(StatusCode::CONFLICT, message)
54 }
55 pub fn gone<T>(message: impl Into<String>) -> Result<T, Self> {
56 Self::err(StatusCode::GONE, message)
57 }
58 pub fn length_required<T>(message: impl Into<String>) -> Result<T, Self> {
59 Self::err(StatusCode::LENGTH_REQUIRED, message)
60 }
61 pub fn precondition_failed<T>(message: impl Into<String>) -> Result<T, Self> {
62 Self::err(StatusCode::PRECONDITION_FAILED, message)
63 }
64 pub fn payload_too_large<T>(message: impl Into<String>) -> Result<T, Self> {
65 Self::err(StatusCode::PAYLOAD_TOO_LARGE, message)
66 }
67 pub fn uri_too_long<T>(message: impl Into<String>) -> Result<T, Self> {
68 Self::err(StatusCode::URI_TOO_LONG, message)
69 }
70 pub fn unsupported_media_type<T>(message: impl Into<String>) -> Result<T, Self> {
71 Self::err(StatusCode::UNSUPPORTED_MEDIA_TYPE, message)
72 }
73 pub fn im_a_teapot<T>(message: impl Into<String>) -> Result<T, Self> {
74 Self::err(StatusCode::IM_A_TEAPOT, message)
75 }
76 pub fn too_many_requests<T>(message: impl Into<String>) -> Result<T, Self> {
77 Self::err(StatusCode::TOO_MANY_REQUESTS, message)
78 }
79
80 pub fn internal_server_error<T>(message: impl Into<String>) -> Result<T, Self> {
82 Self::err(StatusCode::INTERNAL_SERVER_ERROR, message)
83 }
84 pub fn not_implemented<T>(message: impl Into<String>) -> Result<T, Self> {
85 Self::err(StatusCode::NOT_IMPLEMENTED, message)
86 }
87 pub fn bad_gateway<T>(message: impl Into<String>) -> Result<T, Self> {
88 Self::err(StatusCode::BAD_GATEWAY, message)
89 }
90 pub fn service_unavailable<T>(message: impl Into<String>) -> Result<T, Self> {
91 Self::err(StatusCode::SERVICE_UNAVAILABLE, message)
92 }
93 pub fn gateway_timeout<T>(message: impl Into<String>) -> Result<T, Self> {
94 Self::err(StatusCode::GATEWAY_TIMEOUT, message)
95 }
96 pub fn http_version_not_supported<T>(message: impl Into<String>) -> Result<T, Self> {
97 Self::err(StatusCode::HTTP_VERSION_NOT_SUPPORTED, message)
98 }
99
100 pub fn ok<T>(message: impl Into<String>) -> Result<T, Self> {
102 Self::err(StatusCode::OK, message)
103 }
104 pub fn created<T>(message: impl Into<String>) -> Result<T, Self> {
105 Self::err(StatusCode::CREATED, message)
106 }
107 pub fn accepted<T>(message: impl Into<String>) -> Result<T, Self> {
108 Self::err(StatusCode::ACCEPTED, message)
109 }
110 pub fn moved_permanently<T>(message: impl Into<String>) -> Result<T, Self> {
111 Self::err(StatusCode::MOVED_PERMANENTLY, message)
112 }
113 pub fn found<T>(message: impl Into<String>) -> Result<T, Self> {
114 Self::err(StatusCode::FOUND, message)
115 }
116 pub fn see_other<T>(message: impl Into<String>) -> Result<T, Self> {
117 Self::err(StatusCode::SEE_OTHER, message)
118 }
119 pub fn not_modified<T>(message: impl Into<String>) -> Result<T, Self> {
120 Self::err(StatusCode::NOT_MODIFIED, message)
121 }
122}
123
124impl fmt::Display for HttpError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match &self.message {
127 Some(msg) => write!(f, "{}: {}", self.status, msg),
128 None => write!(f, "{}", self.status),
129 }
130 }
131}
132
133impl std::error::Error for HttpError {}
134
135pub trait OrHttpError<T, M>: Sized {
137 fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError>;
138
139 fn or_bad_request(self, message: impl Into<String>) -> Result<T, HttpError> {
141 self.or_http_error(StatusCode::BAD_REQUEST, message)
142 }
143 fn or_unauthorized(self, message: impl Into<String>) -> Result<T, HttpError> {
144 self.or_http_error(StatusCode::UNAUTHORIZED, message)
145 }
146 fn or_forbidden(self, message: impl Into<String>) -> Result<T, HttpError> {
147 self.or_http_error(StatusCode::FORBIDDEN, message)
148 }
149 fn or_not_found(self, message: impl Into<String>) -> Result<T, HttpError> {
150 self.or_http_error(StatusCode::NOT_FOUND, message)
151 }
152 fn or_internal_server_error(self, message: impl Into<String>) -> Result<T, HttpError> {
153 self.or_http_error(StatusCode::INTERNAL_SERVER_ERROR, message)
154 }
155}
156
157impl<T, E> OrHttpError<T, ()> for Result<T, E>
158where
159 E: std::error::Error + Send + Sync + 'static,
160{
161 fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError> {
162 self.map_err(|_| HttpError {
163 status,
164 message: Some(message.into()),
165 })
166 }
167}
168
169impl<T> OrHttpError<T, ()> for Option<T> {
170 fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError> {
171 self.ok_or_else(|| HttpError {
172 status,
173 message: Some(message.into()),
174 })
175 }
176}
177
178impl OrHttpError<(), ()> for bool {
179 fn or_http_error(
180 self,
181 status: StatusCode,
182 message: impl Into<String>,
183 ) -> Result<(), HttpError> {
184 if self {
185 Ok(())
186 } else {
187 Err(HttpError {
188 status,
189 message: Some(message.into()),
190 })
191 }
192 }
193}
194
195pub struct AnyhowMarker;
196impl<T> OrHttpError<T, AnyhowMarker> for Result<T, anyhow::Error> {
197 fn or_http_error(self, status: StatusCode, message: impl Into<String>) -> Result<T, HttpError> {
198 self.map_err(|_| HttpError {
199 status,
200 message: Some(message.into()),
201 })
202 }
203}
204
205impl IntoResponse for HttpError {
206 fn into_response(self) -> axum_core::response::Response {
207 let body = match &self.message {
208 Some(msg) => msg.clone(),
209 None => self
210 .status
211 .canonical_reason()
212 .unwrap_or("Unknown error")
213 .to_string(),
214 };
215 (self.status, body).into_response()
216 }
217}