1#![allow(clippy::new_ret_no_self)]
2
3use std::collections::BTreeMap;
4
5use http::StatusCode;
6
7#[derive(Debug)]
8pub struct HttpException {
10 pub status: StatusCode,
12 pub message: String,
14 pub error: &'static str,
16 pub errors: Option<BTreeMap<String, Vec<String>>>,
18 pub source: Option<anyhow::Error>,
20}
21
22impl HttpException {
23 pub fn new(status: StatusCode, error: &'static str, message: impl Into<String>) -> Self {
25 Self {
26 status,
27 error,
28 message: message.into(),
29 errors: None,
30 source: None,
31 }
32 }
33
34 pub fn with_source(mut self, err: impl Into<anyhow::Error>) -> Self {
36 self.source = Some(err.into());
37 self
38 }
39
40 pub fn with_errors(mut self, errors: BTreeMap<String, Vec<String>>) -> Self {
42 self.errors = Some(errors);
43 self
44 }
45}
46
47pub(crate) fn startup_error(message: impl Into<String>) -> HttpException {
48 HttpException::new(
49 StatusCode::INTERNAL_SERVER_ERROR,
50 "Internal Server Error",
51 message,
52 )
53}
54
55pub struct BadRequestException;
57impl BadRequestException {
58 pub fn new(message: impl Into<String>) -> HttpException {
60 HttpException::new(StatusCode::BAD_REQUEST, "Bad Request", message)
61 }
62}
63
64pub struct UnauthorizedException;
66impl UnauthorizedException {
67 pub fn new(message: impl Into<String>) -> HttpException {
69 HttpException::new(StatusCode::UNAUTHORIZED, "Unauthorized", message)
70 }
71}
72
73pub struct PaymentRequiredException;
75impl PaymentRequiredException {
76 pub fn new(message: impl Into<String>) -> HttpException {
78 HttpException::new(StatusCode::PAYMENT_REQUIRED, "Payment Required", message)
79 }
80}
81
82pub struct ForbiddenException;
84impl ForbiddenException {
85 pub fn new(message: impl Into<String>) -> HttpException {
87 HttpException::new(StatusCode::FORBIDDEN, "Forbidden", message)
88 }
89}
90
91pub struct NotFoundException;
93impl NotFoundException {
94 pub fn new(message: impl Into<String>) -> HttpException {
96 HttpException::new(StatusCode::NOT_FOUND, "Not Found", message)
97 }
98}
99
100pub struct MethodNotAllowedException;
102impl MethodNotAllowedException {
103 pub fn new(message: impl Into<String>) -> HttpException {
105 HttpException::new(
106 StatusCode::METHOD_NOT_ALLOWED,
107 "Method Not Allowed",
108 message,
109 )
110 }
111}
112
113pub struct NotAcceptableException;
115impl NotAcceptableException {
116 pub fn new(message: impl Into<String>) -> HttpException {
118 HttpException::new(StatusCode::NOT_ACCEPTABLE, "Not Acceptable", message)
119 }
120}
121
122pub struct ProxyAuthenticationRequiredException;
124impl ProxyAuthenticationRequiredException {
125 pub fn new(message: impl Into<String>) -> HttpException {
127 HttpException::new(
128 StatusCode::PROXY_AUTHENTICATION_REQUIRED,
129 "Proxy Authentication Required",
130 message,
131 )
132 }
133}
134
135pub struct RequestTimeoutException;
137impl RequestTimeoutException {
138 pub fn new(message: impl Into<String>) -> HttpException {
140 HttpException::new(StatusCode::REQUEST_TIMEOUT, "Request Timeout", message)
141 }
142}
143
144pub struct ConflictException;
146impl ConflictException {
147 pub fn new(message: impl Into<String>) -> HttpException {
149 HttpException::new(StatusCode::CONFLICT, "Conflict", message)
150 }
151}
152
153pub struct GoneException;
155impl GoneException {
156 pub fn new(message: impl Into<String>) -> HttpException {
158 HttpException::new(StatusCode::GONE, "Gone", message)
159 }
160}
161
162pub struct LengthRequiredException;
164impl LengthRequiredException {
165 pub fn new(message: impl Into<String>) -> HttpException {
167 HttpException::new(StatusCode::LENGTH_REQUIRED, "Length Required", message)
168 }
169}
170
171pub struct PreconditionFailedException;
173impl PreconditionFailedException {
174 pub fn new(message: impl Into<String>) -> HttpException {
176 HttpException::new(
177 StatusCode::PRECONDITION_FAILED,
178 "Precondition Failed",
179 message,
180 )
181 }
182}
183
184pub struct PayloadTooLargeException;
186impl PayloadTooLargeException {
187 pub fn new(message: impl Into<String>) -> HttpException {
189 HttpException::new(StatusCode::PAYLOAD_TOO_LARGE, "Payload Too Large", message)
190 }
191}
192
193pub struct UriTooLongException;
195impl UriTooLongException {
196 pub fn new(message: impl Into<String>) -> HttpException {
198 HttpException::new(StatusCode::URI_TOO_LONG, "URI Too Long", message)
199 }
200}
201
202pub struct UnsupportedMediaTypeException;
204impl UnsupportedMediaTypeException {
205 pub fn new(message: impl Into<String>) -> HttpException {
207 HttpException::new(
208 StatusCode::UNSUPPORTED_MEDIA_TYPE,
209 "Unsupported Media Type",
210 message,
211 )
212 }
213}
214
215pub struct RangeNotSatisfiableException;
217impl RangeNotSatisfiableException {
218 pub fn new(message: impl Into<String>) -> HttpException {
220 HttpException::new(
221 StatusCode::RANGE_NOT_SATISFIABLE,
222 "Range Not Satisfiable",
223 message,
224 )
225 }
226}
227
228pub struct ExpectationFailedException;
230impl ExpectationFailedException {
231 pub fn new(message: impl Into<String>) -> HttpException {
233 HttpException::new(
234 StatusCode::EXPECTATION_FAILED,
235 "Expectation Failed",
236 message,
237 )
238 }
239}
240
241pub struct ImATeapotException;
243impl ImATeapotException {
244 pub fn new(message: impl Into<String>) -> HttpException {
246 HttpException::new(StatusCode::IM_A_TEAPOT, "I'm a teapot", message)
247 }
248}
249
250pub struct MisdirectedRequestException;
252impl MisdirectedRequestException {
253 pub fn new(message: impl Into<String>) -> HttpException {
255 HttpException::new(
256 StatusCode::MISDIRECTED_REQUEST,
257 "Misdirected Request",
258 message,
259 )
260 }
261}
262
263pub struct UnprocessableEntityException;
265impl UnprocessableEntityException {
266 pub fn new(message: impl Into<String>) -> HttpException {
268 HttpException::new(
269 StatusCode::UNPROCESSABLE_ENTITY,
270 "Unprocessable Entity",
271 message,
272 )
273 }
274}
275
276pub struct LockedException;
278impl LockedException {
279 pub fn new(message: impl Into<String>) -> HttpException {
281 HttpException::new(StatusCode::LOCKED, "Locked", message)
282 }
283}
284
285pub struct FailedDependencyException;
287impl FailedDependencyException {
288 pub fn new(message: impl Into<String>) -> HttpException {
290 HttpException::new(StatusCode::FAILED_DEPENDENCY, "Failed Dependency", message)
291 }
292}
293
294pub struct TooEarlyException;
296impl TooEarlyException {
297 pub fn new(message: impl Into<String>) -> HttpException {
299 HttpException::new(StatusCode::TOO_EARLY, "Too Early", message)
300 }
301}
302
303pub struct UpgradeRequiredException;
305impl UpgradeRequiredException {
306 pub fn new(message: impl Into<String>) -> HttpException {
308 HttpException::new(StatusCode::UPGRADE_REQUIRED, "Upgrade Required", message)
309 }
310}
311
312pub struct PreconditionRequiredException;
314impl PreconditionRequiredException {
315 pub fn new(message: impl Into<String>) -> HttpException {
317 HttpException::new(
318 StatusCode::PRECONDITION_REQUIRED,
319 "Precondition Required",
320 message,
321 )
322 }
323}
324
325pub struct TooManyRequestsException;
327impl TooManyRequestsException {
328 pub fn new(message: impl Into<String>) -> HttpException {
330 HttpException::new(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests", message)
331 }
332}
333
334pub struct RequestHeaderFieldsTooLargeException;
336impl RequestHeaderFieldsTooLargeException {
337 pub fn new(message: impl Into<String>) -> HttpException {
339 HttpException::new(
340 StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
341 "Request Header Fields Too Large",
342 message,
343 )
344 }
345}
346
347pub struct UnavailableForLegalReasonsException;
349impl UnavailableForLegalReasonsException {
350 pub fn new(message: impl Into<String>) -> HttpException {
352 HttpException::new(
353 StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
354 "Unavailable For Legal Reasons",
355 message,
356 )
357 }
358}
359
360pub struct InternalServerErrorException;
362impl InternalServerErrorException {
363 pub fn new(err: impl Into<anyhow::Error>) -> HttpException {
365 HttpException::new(
366 StatusCode::INTERNAL_SERVER_ERROR,
367 "Internal Server Error",
368 "Internal Server Error",
369 )
370 .with_source(err)
371 }
372}
373
374impl<E> From<E> for HttpException
375where
376 E: Into<anyhow::Error>,
377{
378 fn from(err: E) -> Self {
379 let err = err.into();
380
381 #[cfg(feature = "validator")]
382 if let Some(validation_errors) = err.downcast_ref::<validator::ValidationErrors>() {
383 return BadRequestException::new("Validation failed")
384 .with_errors(format_validation_errors(validation_errors));
385 }
386
387 InternalServerErrorException::new(err)
388 }
389}
390
391#[cfg(feature = "validator")]
392fn format_validation_errors(err: &validator::ValidationErrors) -> BTreeMap<String, Vec<String>> {
393 let mut errors = BTreeMap::new();
394 collect_validation_errors("", err, &mut errors);
395 errors
396}
397
398#[cfg(feature = "validator")]
399fn collect_validation_errors(
400 prefix: &str,
401 err: &validator::ValidationErrors,
402 field_errors: &mut BTreeMap<String, Vec<String>>,
403) {
404 let mut fields = err.errors().iter().collect::<Vec<_>>();
405 fields.sort_by(|(left, _), (right, _)| left.as_ref().cmp(right.as_ref()));
406
407 for (field, kind) in fields {
408 let path = if prefix.is_empty() {
409 (*field).to_string()
410 } else {
411 format!("{prefix}.{field}")
412 };
413
414 collect_validation_error_kind(&path, kind, field_errors);
415 }
416}
417
418#[cfg(feature = "validator")]
419fn collect_validation_error_kind(
420 path: &str,
421 kind: &validator::ValidationErrorsKind,
422 field_errors: &mut BTreeMap<String, Vec<String>>,
423) {
424 match kind {
425 validator::ValidationErrorsKind::Field(errors) => {
426 for error in errors {
427 field_errors
428 .entry(path.to_string())
429 .or_default()
430 .push(format_validation_error(error));
431 }
432 }
433 validator::ValidationErrorsKind::Struct(errors) => {
434 collect_validation_errors(path, errors, field_errors);
435 }
436 validator::ValidationErrorsKind::List(errors) => {
437 for (index, errors) in errors {
438 collect_validation_errors(&format!("{path}[{index}]"), errors, field_errors);
439 }
440 }
441 }
442}
443
444#[cfg(feature = "validator")]
445fn format_validation_error(error: &validator::ValidationError) -> String {
446 if let Some(message) = &error.message {
447 return message.to_string();
448 }
449
450 match error.code.as_ref() {
451 "email" => "must be a valid email".to_string(),
452 "length" => format_length_validation_error(error),
453 "required" => "is required".to_string(),
454 "url" => "must be a valid URL".to_string(),
455 "regex" => "has an invalid format".to_string(),
456 "contains" => match validation_param(error, "needle") {
457 Some(needle) => format!("must contain {needle}"),
458 None => "must contain the required value".to_string(),
459 },
460 "does_not_contain" => match validation_param(error, "needle") {
461 Some(needle) => format!("must not contain {needle}"),
462 None => "contains a forbidden value".to_string(),
463 },
464 "must_match" => match validation_param(error, "other") {
465 Some(other) => format!("must match {other}"),
466 None => "does not match".to_string(),
467 },
468 "ip" => "must be a valid IP address".to_string(),
469 "ipv4" => "must be a valid IPv4 address".to_string(),
470 "ipv6" => "must be a valid IPv6 address".to_string(),
471 code => format!("is invalid ({code})"),
472 }
473}
474
475#[cfg(feature = "validator")]
476fn format_length_validation_error(error: &validator::ValidationError) -> String {
477 match (
478 validation_param(error, "equal"),
479 validation_param(error, "min"),
480 validation_param(error, "max"),
481 ) {
482 (Some(equal), _, _) => format!("must be exactly {equal} characters"),
483 (None, Some(min), Some(max)) => {
484 format!("must be between {min} and {max} characters")
485 }
486 (None, Some(min), None) => format!("must be at least {min} characters"),
487 (None, None, Some(max)) => format!("must be at most {max} characters"),
488 (None, None, None) => "has an invalid length".to_string(),
489 }
490}
491
492#[cfg(feature = "validator")]
493fn validation_param(error: &validator::ValidationError, name: &str) -> Option<String> {
494 let value = error.params.get(name)?;
495
496 match value {
497 serde_json::Value::String(value) => Some(value.clone()),
498 serde_json::Value::Number(value) => Some(value.to_string()),
499 serde_json::Value::Bool(value) => Some(value.to_string()),
500 serde_json::Value::Null => None,
501 value => Some(value.to_string()),
502 }
503}
504
505pub struct NotImplementedException;
507impl NotImplementedException {
508 pub fn new(message: impl Into<String>) -> HttpException {
510 HttpException::new(StatusCode::NOT_IMPLEMENTED, "Not Implemented", message)
511 }
512}
513
514pub struct BadGatewayException;
516impl BadGatewayException {
517 pub fn new(message: impl Into<String>) -> HttpException {
519 HttpException::new(StatusCode::BAD_GATEWAY, "Bad Gateway", message)
520 }
521}
522
523pub struct ServiceUnavailableException;
525impl ServiceUnavailableException {
526 pub fn new(message: impl Into<String>) -> HttpException {
528 HttpException::new(
529 StatusCode::SERVICE_UNAVAILABLE,
530 "Service Unavailable",
531 message,
532 )
533 }
534}
535
536pub struct GatewayTimeoutException;
538impl GatewayTimeoutException {
539 pub fn new(message: impl Into<String>) -> HttpException {
541 HttpException::new(StatusCode::GATEWAY_TIMEOUT, "Gateway Timeout", message)
542 }
543}
544
545pub struct HttpVersionNotSupportedException;
547impl HttpVersionNotSupportedException {
548 pub fn new(message: impl Into<String>) -> HttpException {
550 HttpException::new(
551 StatusCode::HTTP_VERSION_NOT_SUPPORTED,
552 "HTTP Version Not Supported",
553 message,
554 )
555 }
556}
557
558pub struct VariantAlsoNegotiatesException;
560impl VariantAlsoNegotiatesException {
561 pub fn new(message: impl Into<String>) -> HttpException {
563 HttpException::new(
564 StatusCode::VARIANT_ALSO_NEGOTIATES,
565 "Variant Also Negotiates",
566 message,
567 )
568 }
569}
570
571pub struct InsufficientStorageException;
573impl InsufficientStorageException {
574 pub fn new(message: impl Into<String>) -> HttpException {
576 HttpException::new(
577 StatusCode::INSUFFICIENT_STORAGE,
578 "Insufficient Storage",
579 message,
580 )
581 }
582}
583
584pub struct LoopDetectedException;
586impl LoopDetectedException {
587 pub fn new(message: impl Into<String>) -> HttpException {
589 HttpException::new(StatusCode::LOOP_DETECTED, "Loop Detected", message)
590 }
591}
592
593pub struct NotExtendedException;
595impl NotExtendedException {
596 pub fn new(message: impl Into<String>) -> HttpException {
598 HttpException::new(StatusCode::NOT_EXTENDED, "Not Extended", message)
599 }
600}
601
602pub struct NetworkAuthenticationRequiredException;
604impl NetworkAuthenticationRequiredException {
605 pub fn new(message: impl Into<String>) -> HttpException {
607 HttpException::new(
608 StatusCode::NETWORK_AUTHENTICATION_REQUIRED,
609 "Network Authentication Required",
610 message,
611 )
612 }
613}