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
374#[cfg(feature = "sqlx")]
375impl From<sqlx::Error> for HttpException {
376 fn from(err: sqlx::Error) -> Self {
377 InternalServerErrorException::new(err)
378 }
379}
380
381#[cfg(feature = "validator")]
382impl From<validator::ValidationErrors> for HttpException {
383 fn from(err: validator::ValidationErrors) -> Self {
384 BadRequestException::new("Validation failed").with_errors(format_validation_errors(&err))
385 }
386}
387
388#[cfg(feature = "validator")]
389fn format_validation_errors(err: &validator::ValidationErrors) -> BTreeMap<String, Vec<String>> {
390 let mut errors = BTreeMap::new();
391 collect_validation_errors("", err, &mut errors);
392 errors
393}
394
395#[cfg(feature = "validator")]
396fn collect_validation_errors(
397 prefix: &str,
398 err: &validator::ValidationErrors,
399 field_errors: &mut BTreeMap<String, Vec<String>>,
400) {
401 let mut fields = err.errors().iter().collect::<Vec<_>>();
402 fields.sort_by(|(left, _), (right, _)| left.as_ref().cmp(right.as_ref()));
403
404 for (field, kind) in fields {
405 let path = if prefix.is_empty() {
406 (*field).to_string()
407 } else {
408 format!("{prefix}.{field}")
409 };
410
411 collect_validation_error_kind(&path, kind, field_errors);
412 }
413}
414
415#[cfg(feature = "validator")]
416fn collect_validation_error_kind(
417 path: &str,
418 kind: &validator::ValidationErrorsKind,
419 field_errors: &mut BTreeMap<String, Vec<String>>,
420) {
421 match kind {
422 validator::ValidationErrorsKind::Field(errors) => {
423 for error in errors {
424 field_errors
425 .entry(path.to_string())
426 .or_default()
427 .push(format_validation_error(error));
428 }
429 }
430 validator::ValidationErrorsKind::Struct(errors) => {
431 collect_validation_errors(path, errors, field_errors);
432 }
433 validator::ValidationErrorsKind::List(errors) => {
434 for (index, errors) in errors {
435 collect_validation_errors(&format!("{path}[{index}]"), errors, field_errors);
436 }
437 }
438 }
439}
440
441#[cfg(feature = "validator")]
442fn format_validation_error(error: &validator::ValidationError) -> String {
443 if let Some(message) = &error.message {
444 return message.to_string();
445 }
446
447 match error.code.as_ref() {
448 "email" => "must be a valid email".to_string(),
449 "length" => format_length_validation_error(error),
450 "required" => "is required".to_string(),
451 "url" => "must be a valid URL".to_string(),
452 "regex" => "has an invalid format".to_string(),
453 "contains" => match validation_param(error, "needle") {
454 Some(needle) => format!("must contain {needle}"),
455 None => "must contain the required value".to_string(),
456 },
457 "does_not_contain" => match validation_param(error, "needle") {
458 Some(needle) => format!("must not contain {needle}"),
459 None => "contains a forbidden value".to_string(),
460 },
461 "must_match" => match validation_param(error, "other") {
462 Some(other) => format!("must match {other}"),
463 None => "does not match".to_string(),
464 },
465 "ip" => "must be a valid IP address".to_string(),
466 "ipv4" => "must be a valid IPv4 address".to_string(),
467 "ipv6" => "must be a valid IPv6 address".to_string(),
468 code => format!("is invalid ({code})"),
469 }
470}
471
472#[cfg(feature = "validator")]
473fn format_length_validation_error(error: &validator::ValidationError) -> String {
474 match (
475 validation_param(error, "equal"),
476 validation_param(error, "min"),
477 validation_param(error, "max"),
478 ) {
479 (Some(equal), _, _) => format!("must be exactly {equal} characters"),
480 (None, Some(min), Some(max)) => {
481 format!("must be between {min} and {max} characters")
482 }
483 (None, Some(min), None) => format!("must be at least {min} characters"),
484 (None, None, Some(max)) => format!("must be at most {max} characters"),
485 (None, None, None) => "has an invalid length".to_string(),
486 }
487}
488
489#[cfg(feature = "validator")]
490fn validation_param(error: &validator::ValidationError, name: &str) -> Option<String> {
491 let value = error.params.get(name)?;
492
493 match value {
494 serde_json::Value::String(value) => Some(value.clone()),
495 serde_json::Value::Number(value) => Some(value.to_string()),
496 serde_json::Value::Bool(value) => Some(value.to_string()),
497 serde_json::Value::Null => None,
498 value => Some(value.to_string()),
499 }
500}
501
502pub struct NotImplementedException;
504impl NotImplementedException {
505 pub fn new(message: impl Into<String>) -> HttpException {
507 HttpException::new(StatusCode::NOT_IMPLEMENTED, "Not Implemented", message)
508 }
509}
510
511pub struct BadGatewayException;
513impl BadGatewayException {
514 pub fn new(message: impl Into<String>) -> HttpException {
516 HttpException::new(StatusCode::BAD_GATEWAY, "Bad Gateway", message)
517 }
518}
519
520pub struct ServiceUnavailableException;
522impl ServiceUnavailableException {
523 pub fn new(message: impl Into<String>) -> HttpException {
525 HttpException::new(
526 StatusCode::SERVICE_UNAVAILABLE,
527 "Service Unavailable",
528 message,
529 )
530 }
531}
532
533pub struct GatewayTimeoutException;
535impl GatewayTimeoutException {
536 pub fn new(message: impl Into<String>) -> HttpException {
538 HttpException::new(StatusCode::GATEWAY_TIMEOUT, "Gateway Timeout", message)
539 }
540}
541
542pub struct HttpVersionNotSupportedException;
544impl HttpVersionNotSupportedException {
545 pub fn new(message: impl Into<String>) -> HttpException {
547 HttpException::new(
548 StatusCode::HTTP_VERSION_NOT_SUPPORTED,
549 "HTTP Version Not Supported",
550 message,
551 )
552 }
553}
554
555pub struct VariantAlsoNegotiatesException;
557impl VariantAlsoNegotiatesException {
558 pub fn new(message: impl Into<String>) -> HttpException {
560 HttpException::new(
561 StatusCode::VARIANT_ALSO_NEGOTIATES,
562 "Variant Also Negotiates",
563 message,
564 )
565 }
566}
567
568pub struct InsufficientStorageException;
570impl InsufficientStorageException {
571 pub fn new(message: impl Into<String>) -> HttpException {
573 HttpException::new(
574 StatusCode::INSUFFICIENT_STORAGE,
575 "Insufficient Storage",
576 message,
577 )
578 }
579}
580
581pub struct LoopDetectedException;
583impl LoopDetectedException {
584 pub fn new(message: impl Into<String>) -> HttpException {
586 HttpException::new(StatusCode::LOOP_DETECTED, "Loop Detected", message)
587 }
588}
589
590pub struct NotExtendedException;
592impl NotExtendedException {
593 pub fn new(message: impl Into<String>) -> HttpException {
595 HttpException::new(StatusCode::NOT_EXTENDED, "Not Extended", message)
596 }
597}
598
599pub struct NetworkAuthenticationRequiredException;
601impl NetworkAuthenticationRequiredException {
602 pub fn new(message: impl Into<String>) -> HttpException {
604 HttpException::new(
605 StatusCode::NETWORK_AUTHENTICATION_REQUIRED,
606 "Network Authentication Required",
607 message,
608 )
609 }
610}