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
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
505/// Public Caelix type `NotImplementedException`.
506pub struct NotImplementedException;
507impl NotImplementedException {
508    /// Runs the `new` public API operation.
509    pub fn new(message: impl Into<String>) -> HttpException {
510        HttpException::new(StatusCode::NOT_IMPLEMENTED, "Not Implemented", message)
511    }
512}
513
514/// Public Caelix type `BadGatewayException`.
515pub struct BadGatewayException;
516impl BadGatewayException {
517    /// Runs the `new` public API operation.
518    pub fn new(message: impl Into<String>) -> HttpException {
519        HttpException::new(StatusCode::BAD_GATEWAY, "Bad Gateway", message)
520    }
521}
522
523/// Public Caelix type `ServiceUnavailableException`.
524pub struct ServiceUnavailableException;
525impl ServiceUnavailableException {
526    /// Runs the `new` public API operation.
527    pub fn new(message: impl Into<String>) -> HttpException {
528        HttpException::new(
529            StatusCode::SERVICE_UNAVAILABLE,
530            "Service Unavailable",
531            message,
532        )
533    }
534}
535
536/// Public Caelix type `GatewayTimeoutException`.
537pub struct GatewayTimeoutException;
538impl GatewayTimeoutException {
539    /// Runs the `new` public API operation.
540    pub fn new(message: impl Into<String>) -> HttpException {
541        HttpException::new(StatusCode::GATEWAY_TIMEOUT, "Gateway Timeout", message)
542    }
543}
544
545/// Public Caelix type `HttpVersionNotSupportedException`.
546pub struct HttpVersionNotSupportedException;
547impl HttpVersionNotSupportedException {
548    /// Runs the `new` public API operation.
549    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
558/// Public Caelix type `VariantAlsoNegotiatesException`.
559pub struct VariantAlsoNegotiatesException;
560impl VariantAlsoNegotiatesException {
561    /// Runs the `new` public API operation.
562    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
571/// Public Caelix type `InsufficientStorageException`.
572pub struct InsufficientStorageException;
573impl InsufficientStorageException {
574    /// Runs the `new` public API operation.
575    pub fn new(message: impl Into<String>) -> HttpException {
576        HttpException::new(
577            StatusCode::INSUFFICIENT_STORAGE,
578            "Insufficient Storage",
579            message,
580        )
581    }
582}
583
584/// Public Caelix type `LoopDetectedException`.
585pub struct LoopDetectedException;
586impl LoopDetectedException {
587    /// Runs the `new` public API operation.
588    pub fn new(message: impl Into<String>) -> HttpException {
589        HttpException::new(StatusCode::LOOP_DETECTED, "Loop Detected", message)
590    }
591}
592
593/// Public Caelix type `NotExtendedException`.
594pub struct NotExtendedException;
595impl NotExtendedException {
596    /// Runs the `new` public API operation.
597    pub fn new(message: impl Into<String>) -> HttpException {
598        HttpException::new(StatusCode::NOT_EXTENDED, "Not Extended", message)
599    }
600}
601
602/// Public Caelix type `NetworkAuthenticationRequiredException`.
603pub struct NetworkAuthenticationRequiredException;
604impl NetworkAuthenticationRequiredException {
605    /// Runs the `new` public API operation.
606    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}