1use std::borrow::Cow;
12use std::error::Error as StdError;
13use std::fmt;
14
15use axum::http::StatusCode;
16use axum::response::{IntoResponse, Response};
17use serde::Serialize;
18
19#[derive(Debug, Serialize, Clone)]
21pub struct FieldError {
22 pub field: String,
23 pub code: String,
24 pub message: String,
25}
26
27#[derive(Debug, Serialize, Clone)]
29pub struct ProblemDetails {
30 #[serde(rename = "type")]
31 pub kind: Cow<'static, str>,
32 pub title: Cow<'static, str>,
33 pub status: u16,
34 pub detail: Cow<'static, str>,
35 #[serde(skip_serializing_if = "Vec::is_empty", default)]
36 pub errors: Vec<FieldError>,
37}
38
39impl ProblemDetails {
40 #[inline]
41 pub fn new(
42 status: u16,
43 kind: &'static str,
44 title: &'static str,
45 detail: impl Into<Cow<'static, str>>,
46 ) -> Self {
47 Self {
48 kind: Cow::Borrowed(kind),
49 title: Cow::Borrowed(title),
50 status,
51 detail: detail.into(),
52 errors: Vec::new(),
53 }
54 }
55 #[inline]
56 pub fn with_errors(mut self, errors: Vec<FieldError>) -> Self {
57 self.errors = errors;
58 self
59 }
60}
61
62pub trait HttpError: StdError + Send + Sync + 'static {
66 fn problem(&self) -> ProblemDetails;
67}
68
69pub struct HttpException(pub Box<dyn HttpError>);
73
74impl fmt::Debug for HttpException {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "HttpException({})", self.0)
77 }
78}
79
80impl fmt::Display for HttpException {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 fmt::Display::fmt(&self.0, f)
83 }
84}
85
86impl<E: HttpError + 'static> From<E> for HttpException {
87 #[inline]
88 fn from(e: E) -> Self {
89 Self(Box::new(e))
90 }
91}
92
93impl IntoResponse for HttpException {
94 fn into_response(self) -> Response {
95 let p = self.0.problem();
96 let status = StatusCode::from_u16(p.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
97 (status, axum::Json(p)).into_response()
98 }
99}
100
101macro_rules! stock_error {
103 ($name:ident, $status:expr, $kind:expr, $title:expr, $default_detail:expr) => {
104 #[derive(Debug, Clone)]
105 pub struct $name {
106 pub detail: Cow<'static, str>,
107 }
108 impl $name {
109 #[inline]
110 pub fn new(detail: impl Into<Cow<'static, str>>) -> Self {
111 Self {
112 detail: detail.into(),
113 }
114 }
115 }
116 impl Default for $name {
117 fn default() -> Self {
118 Self {
119 detail: Cow::Borrowed($default_detail),
120 }
121 }
122 }
123 impl fmt::Display for $name {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "{}: {}", $title, self.detail)
126 }
127 }
128 impl StdError for $name {}
129 impl HttpError for $name {
130 fn problem(&self) -> ProblemDetails {
131 ProblemDetails::new($status, $kind, $title, self.detail.clone())
132 }
133 }
134 };
135}
136
137stock_error!(
138 NotFound,
139 404,
140 "not-found",
141 "Not Found",
142 "resource not found"
143);
144stock_error!(
145 Unauthorized,
146 401,
147 "unauthorized",
148 "Unauthorized",
149 "authentication required"
150);
151stock_error!(Forbidden, 403, "forbidden", "Forbidden", "access denied");
152stock_error!(
153 BadRequest,
154 400,
155 "bad-request",
156 "Bad Request",
157 "malformed request"
158);
159stock_error!(Conflict, 409, "conflict", "Conflict", "resource conflict");
160stock_error!(
161 TooManyRequests,
162 429,
163 "too-many-requests",
164 "Too Many Requests",
165 "rate limit exceeded"
166);
167stock_error!(
168 ServiceUnavailable,
169 503,
170 "service-unavailable",
171 "Service Unavailable",
172 "downstream unavailable"
173);
174stock_error!(
175 Internal,
176 500,
177 "internal",
178 "Internal Server Error",
179 "internal error"
180);
181stock_error!(
182 GatewayTimeout,
183 504,
184 "gateway-timeout",
185 "Gateway Timeout",
186 "handler deadline exceeded"
187);
188
189#[derive(Debug, Clone)]
191pub struct Validation {
192 pub errors: Vec<FieldError>,
193}
194impl Validation {
195 #[inline]
196 pub fn new(errors: Vec<FieldError>) -> Self {
197 Self { errors }
198 }
199}
200impl fmt::Display for Validation {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 write!(f, "validation failed ({} error(s))", self.errors.len())
203 }
204}
205impl StdError for Validation {}
206impl HttpError for Validation {
207 fn problem(&self) -> ProblemDetails {
208 ProblemDetails::new(
209 422,
210 "validation",
211 "Unprocessable Entity",
212 "payload failed validation",
213 )
214 .with_errors(self.errors.clone())
215 }
216}
217
218impl From<validator::ValidationErrors> for Validation {
219 fn from(errs: validator::ValidationErrors) -> Self {
220 let mut out = Vec::new();
221 for (field, kinds) in errs.field_errors() {
222 for k in kinds {
223 let message = k
224 .message
225 .as_ref()
226 .map(|m| m.to_string())
227 .unwrap_or_else(|| format!("failed `{}` rule", k.code));
228 out.push(FieldError {
229 field: field.to_string(),
230 code: k.code.to_string(),
231 message,
232 });
233 }
234 }
235 Self::new(out)
236 }
237}
238
239#[derive(Debug)]
244pub enum Error {
245 NotFound,
246 Unauthorized,
247 Forbidden,
248 TooManyRequests,
249 ServiceUnavailable(&'static str),
250 BadRequest(&'static str),
251 Validation(Vec<FieldError>),
252 Internal(&'static str),
253}
254
255impl fmt::Display for Error {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 match self {
258 Error::NotFound => write!(f, "not found"),
259 Error::Unauthorized => write!(f, "unauthorized"),
260 Error::Forbidden => write!(f, "forbidden"),
261 Error::TooManyRequests => write!(f, "rate limit exceeded"),
262 Error::ServiceUnavailable(d) => write!(f, "service unavailable: {d}"),
263 Error::BadRequest(d) => write!(f, "bad request: {d}"),
264 Error::Validation(_) => write!(f, "validation failed"),
265 Error::Internal(d) => write!(f, "internal: {d}"),
266 }
267 }
268}
269impl StdError for Error {}
270
271impl HttpError for Error {
272 fn problem(&self) -> ProblemDetails {
273 match self {
274 Error::NotFound => NotFound::default().problem(),
275 Error::Unauthorized => Unauthorized::default().problem(),
276 Error::Forbidden => Forbidden::default().problem(),
277 Error::TooManyRequests => TooManyRequests::default().problem(),
278 Error::ServiceUnavailable(d) => ServiceUnavailable::new(*d).problem(),
279 Error::BadRequest(d) => BadRequest::new(*d).problem(),
280 Error::Validation(v) => Validation::new(v.clone()).problem(),
281 Error::Internal(d) => Internal::new(*d).problem(),
282 }
283 }
284}
285
286impl IntoResponse for Error {
287 fn into_response(self) -> Response {
288 HttpException::from(self).into_response()
289 }
290}
291
292impl From<validator::ValidationErrors> for Error {
293 fn from(errs: validator::ValidationErrors) -> Self {
294 Error::Validation(Validation::from(errs).errors)
295 }
296}