Skip to main content

caelix_core/
exception.rs

1#![allow(clippy::new_ret_no_self)]
2
3use std::collections::BTreeMap;
4
5use http::StatusCode;
6
7#[derive(Debug)]
8/// Public Caelix type `HttpException`.
9pub struct HttpException {
10    /// The `status` value.
11    pub status: StatusCode,
12    /// The `message` value.
13    pub message: String,
14    /// The `error` value.
15    pub error: &'static str,
16    /// The `errors` value.
17    pub errors: Option<BTreeMap<String, Vec<String>>>,
18    /// The `source` value.
19    pub source: Option<anyhow::Error>,
20}
21
22impl HttpException {
23    /// Runs the `new` public API operation.
24    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    /// Runs the `with_source` public API operation.
35    pub fn with_source(mut self, err: impl Into<anyhow::Error>) -> Self {
36        self.source = Some(err.into());
37        self
38    }
39
40    /// Runs the `with_errors` public API operation.
41    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
55/// Public Caelix type `BadRequestException`.
56pub struct BadRequestException;
57impl BadRequestException {
58    /// Runs the `new` public API operation.
59    pub fn new(message: impl Into<String>) -> HttpException {
60        HttpException::new(StatusCode::BAD_REQUEST, "Bad Request", message)
61    }
62}
63
64/// Public Caelix type `UnauthorizedException`.
65pub struct UnauthorizedException;
66impl UnauthorizedException {
67    /// Runs the `new` public API operation.
68    pub fn new(message: impl Into<String>) -> HttpException {
69        HttpException::new(StatusCode::UNAUTHORIZED, "Unauthorized", message)
70    }
71}
72
73/// Public Caelix type `PaymentRequiredException`.
74pub struct PaymentRequiredException;
75impl PaymentRequiredException {
76    /// Runs the `new` public API operation.
77    pub fn new(message: impl Into<String>) -> HttpException {
78        HttpException::new(StatusCode::PAYMENT_REQUIRED, "Payment Required", message)
79    }
80}
81
82/// Public Caelix type `ForbiddenException`.
83pub struct ForbiddenException;
84impl ForbiddenException {
85    /// Runs the `new` public API operation.
86    pub fn new(message: impl Into<String>) -> HttpException {
87        HttpException::new(StatusCode::FORBIDDEN, "Forbidden", message)
88    }
89}
90
91/// Public Caelix type `NotFoundException`.
92pub struct NotFoundException;
93impl NotFoundException {
94    /// Runs the `new` public API operation.
95    pub fn new(message: impl Into<String>) -> HttpException {
96        HttpException::new(StatusCode::NOT_FOUND, "Not Found", message)
97    }
98}
99
100/// Public Caelix type `MethodNotAllowedException`.
101pub struct MethodNotAllowedException;
102impl MethodNotAllowedException {
103    /// Runs the `new` public API operation.
104    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
113/// Public Caelix type `NotAcceptableException`.
114pub struct NotAcceptableException;
115impl NotAcceptableException {
116    /// Runs the `new` public API operation.
117    pub fn new(message: impl Into<String>) -> HttpException {
118        HttpException::new(StatusCode::NOT_ACCEPTABLE, "Not Acceptable", message)
119    }
120}
121
122/// Public Caelix type `ProxyAuthenticationRequiredException`.
123pub struct ProxyAuthenticationRequiredException;
124impl ProxyAuthenticationRequiredException {
125    /// Runs the `new` public API operation.
126    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
135/// Public Caelix type `RequestTimeoutException`.
136pub struct RequestTimeoutException;
137impl RequestTimeoutException {
138    /// Runs the `new` public API operation.
139    pub fn new(message: impl Into<String>) -> HttpException {
140        HttpException::new(StatusCode::REQUEST_TIMEOUT, "Request Timeout", message)
141    }
142}
143
144/// Public Caelix type `ConflictException`.
145pub struct ConflictException;
146impl ConflictException {
147    /// Runs the `new` public API operation.
148    pub fn new(message: impl Into<String>) -> HttpException {
149        HttpException::new(StatusCode::CONFLICT, "Conflict", message)
150    }
151}
152
153/// Public Caelix type `GoneException`.
154pub struct GoneException;
155impl GoneException {
156    /// Runs the `new` public API operation.
157    pub fn new(message: impl Into<String>) -> HttpException {
158        HttpException::new(StatusCode::GONE, "Gone", message)
159    }
160}
161
162/// Public Caelix type `LengthRequiredException`.
163pub struct LengthRequiredException;
164impl LengthRequiredException {
165    /// Runs the `new` public API operation.
166    pub fn new(message: impl Into<String>) -> HttpException {
167        HttpException::new(StatusCode::LENGTH_REQUIRED, "Length Required", message)
168    }
169}
170
171/// Public Caelix type `PreconditionFailedException`.
172pub struct PreconditionFailedException;
173impl PreconditionFailedException {
174    /// Runs the `new` public API operation.
175    pub fn new(message: impl Into<String>) -> HttpException {
176        HttpException::new(
177            StatusCode::PRECONDITION_FAILED,
178            "Precondition Failed",
179            message,
180        )
181    }
182}
183
184/// Public Caelix type `PayloadTooLargeException`.
185pub struct PayloadTooLargeException;
186impl PayloadTooLargeException {
187    /// Runs the `new` public API operation.
188    pub fn new(message: impl Into<String>) -> HttpException {
189        HttpException::new(StatusCode::PAYLOAD_TOO_LARGE, "Payload Too Large", message)
190    }
191}
192
193/// Public Caelix type `UriTooLongException`.
194pub struct UriTooLongException;
195impl UriTooLongException {
196    /// Runs the `new` public API operation.
197    pub fn new(message: impl Into<String>) -> HttpException {
198        HttpException::new(StatusCode::URI_TOO_LONG, "URI Too Long", message)
199    }
200}
201
202/// Public Caelix type `UnsupportedMediaTypeException`.
203pub struct UnsupportedMediaTypeException;
204impl UnsupportedMediaTypeException {
205    /// Runs the `new` public API operation.
206    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
215/// Public Caelix type `RangeNotSatisfiableException`.
216pub struct RangeNotSatisfiableException;
217impl RangeNotSatisfiableException {
218    /// Runs the `new` public API operation.
219    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
228/// Public Caelix type `ExpectationFailedException`.
229pub struct ExpectationFailedException;
230impl ExpectationFailedException {
231    /// Runs the `new` public API operation.
232    pub fn new(message: impl Into<String>) -> HttpException {
233        HttpException::new(
234            StatusCode::EXPECTATION_FAILED,
235            "Expectation Failed",
236            message,
237        )
238    }
239}
240
241/// Public Caelix type `ImATeapotException`.
242pub struct ImATeapotException;
243impl ImATeapotException {
244    /// Runs the `new` public API operation.
245    pub fn new(message: impl Into<String>) -> HttpException {
246        HttpException::new(StatusCode::IM_A_TEAPOT, "I'm a teapot", message)
247    }
248}
249
250/// Public Caelix type `MisdirectedRequestException`.
251pub struct MisdirectedRequestException;
252impl MisdirectedRequestException {
253    /// Runs the `new` public API operation.
254    pub fn new(message: impl Into<String>) -> HttpException {
255        HttpException::new(
256            StatusCode::MISDIRECTED_REQUEST,
257            "Misdirected Request",
258            message,
259        )
260    }
261}
262
263/// Public Caelix type `UnprocessableEntityException`.
264pub struct UnprocessableEntityException;
265impl UnprocessableEntityException {
266    /// Runs the `new` public API operation.
267    pub fn new(message: impl Into<String>) -> HttpException {
268        HttpException::new(
269            StatusCode::UNPROCESSABLE_ENTITY,
270            "Unprocessable Entity",
271            message,
272        )
273    }
274}
275
276/// Public Caelix type `LockedException`.
277pub struct LockedException;
278impl LockedException {
279    /// Runs the `new` public API operation.
280    pub fn new(message: impl Into<String>) -> HttpException {
281        HttpException::new(StatusCode::LOCKED, "Locked", message)
282    }
283}
284
285/// Public Caelix type `FailedDependencyException`.
286pub struct FailedDependencyException;
287impl FailedDependencyException {
288    /// Runs the `new` public API operation.
289    pub fn new(message: impl Into<String>) -> HttpException {
290        HttpException::new(StatusCode::FAILED_DEPENDENCY, "Failed Dependency", message)
291    }
292}
293
294/// Public Caelix type `TooEarlyException`.
295pub struct TooEarlyException;
296impl TooEarlyException {
297    /// Runs the `new` public API operation.
298    pub fn new(message: impl Into<String>) -> HttpException {
299        HttpException::new(StatusCode::TOO_EARLY, "Too Early", message)
300    }
301}
302
303/// Public Caelix type `UpgradeRequiredException`.
304pub struct UpgradeRequiredException;
305impl UpgradeRequiredException {
306    /// Runs the `new` public API operation.
307    pub fn new(message: impl Into<String>) -> HttpException {
308        HttpException::new(StatusCode::UPGRADE_REQUIRED, "Upgrade Required", message)
309    }
310}
311
312/// Public Caelix type `PreconditionRequiredException`.
313pub struct PreconditionRequiredException;
314impl PreconditionRequiredException {
315    /// Runs the `new` public API operation.
316    pub fn new(message: impl Into<String>) -> HttpException {
317        HttpException::new(
318            StatusCode::PRECONDITION_REQUIRED,
319            "Precondition Required",
320            message,
321        )
322    }
323}
324
325/// Public Caelix type `TooManyRequestsException`.
326pub struct TooManyRequestsException;
327impl TooManyRequestsException {
328    /// Runs the `new` public API operation.
329    pub fn new(message: impl Into<String>) -> HttpException {
330        HttpException::new(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests", message)
331    }
332}
333
334/// Public Caelix type `RequestHeaderFieldsTooLargeException`.
335pub struct RequestHeaderFieldsTooLargeException;
336impl RequestHeaderFieldsTooLargeException {
337    /// Runs the `new` public API operation.
338    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
347/// Public Caelix type `UnavailableForLegalReasonsException`.
348pub struct UnavailableForLegalReasonsException;
349impl UnavailableForLegalReasonsException {
350    /// Runs the `new` public API operation.
351    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
360/// Public Caelix type `InternalServerErrorException`.
361pub struct InternalServerErrorException;
362impl InternalServerErrorException {
363    /// Runs the `new` public API operation.
364    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
502/// Public Caelix type `NotImplementedException`.
503pub struct NotImplementedException;
504impl NotImplementedException {
505    /// Runs the `new` public API operation.
506    pub fn new(message: impl Into<String>) -> HttpException {
507        HttpException::new(StatusCode::NOT_IMPLEMENTED, "Not Implemented", message)
508    }
509}
510
511/// Public Caelix type `BadGatewayException`.
512pub struct BadGatewayException;
513impl BadGatewayException {
514    /// Runs the `new` public API operation.
515    pub fn new(message: impl Into<String>) -> HttpException {
516        HttpException::new(StatusCode::BAD_GATEWAY, "Bad Gateway", message)
517    }
518}
519
520/// Public Caelix type `ServiceUnavailableException`.
521pub struct ServiceUnavailableException;
522impl ServiceUnavailableException {
523    /// Runs the `new` public API operation.
524    pub fn new(message: impl Into<String>) -> HttpException {
525        HttpException::new(
526            StatusCode::SERVICE_UNAVAILABLE,
527            "Service Unavailable",
528            message,
529        )
530    }
531}
532
533/// Public Caelix type `GatewayTimeoutException`.
534pub struct GatewayTimeoutException;
535impl GatewayTimeoutException {
536    /// Runs the `new` public API operation.
537    pub fn new(message: impl Into<String>) -> HttpException {
538        HttpException::new(StatusCode::GATEWAY_TIMEOUT, "Gateway Timeout", message)
539    }
540}
541
542/// Public Caelix type `HttpVersionNotSupportedException`.
543pub struct HttpVersionNotSupportedException;
544impl HttpVersionNotSupportedException {
545    /// Runs the `new` public API operation.
546    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
555/// Public Caelix type `VariantAlsoNegotiatesException`.
556pub struct VariantAlsoNegotiatesException;
557impl VariantAlsoNegotiatesException {
558    /// Runs the `new` public API operation.
559    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
568/// Public Caelix type `InsufficientStorageException`.
569pub struct InsufficientStorageException;
570impl InsufficientStorageException {
571    /// Runs the `new` public API operation.
572    pub fn new(message: impl Into<String>) -> HttpException {
573        HttpException::new(
574            StatusCode::INSUFFICIENT_STORAGE,
575            "Insufficient Storage",
576            message,
577        )
578    }
579}
580
581/// Public Caelix type `LoopDetectedException`.
582pub struct LoopDetectedException;
583impl LoopDetectedException {
584    /// Runs the `new` public API operation.
585    pub fn new(message: impl Into<String>) -> HttpException {
586        HttpException::new(StatusCode::LOOP_DETECTED, "Loop Detected", message)
587    }
588}
589
590/// Public Caelix type `NotExtendedException`.
591pub struct NotExtendedException;
592impl NotExtendedException {
593    /// Runs the `new` public API operation.
594    pub fn new(message: impl Into<String>) -> HttpException {
595        HttpException::new(StatusCode::NOT_EXTENDED, "Not Extended", message)
596    }
597}
598
599/// Public Caelix type `NetworkAuthenticationRequiredException`.
600pub struct NetworkAuthenticationRequiredException;
601impl NetworkAuthenticationRequiredException {
602    /// Runs the `new` public API operation.
603    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}