xapi_rs/
error.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::data::DataError;
4use rocket::{
5    http::Status,
6    response::{self, Responder},
7    Request, Response,
8};
9use serde_json::json;
10use std::{borrow::Cow, io};
11use thiserror::Error;
12use tracing::{error, info};
13
14/// Enumeration of different error types raised by this crate.
15#[derive(Debug, Error)]
16pub enum MyError {
17    /// xAPI format violation error.
18    #[error("Failed matching '{input:?}' to {name:?} format pattern")]
19    Format {
20        #[doc(hidden)]
21        input: Cow<'static, str>,
22        #[doc(hidden)]
23        name: Cow<'static, str>,
24    },
25
26    /// Data serialization/deserialization, parsing and validation errors.
27    #[error("General data error: {0}")]
28    Data(
29        #[doc(hidden)]
30        #[from]
31        DataError,
32    ),
33
34    /// Base64 decoding error.
35    #[error("Base64 decode error: {0}")]
36    Base64(
37        #[doc(hidden)]
38        #[from]
39        base64::DecodeError,
40    ),
41
42    /// UTF-8 string conversion error.
43    #[error("UTF8 conversion error: {0}")]
44    UTF8(
45        #[doc(hidden)]
46        #[from]
47        std::str::Utf8Error,
48    ),
49
50    /// Rocket Multipart error.
51    #[error("Multipart/mixed parse error: {0}")]
52    MULTIPART(
53        #[doc(hidden)]
54        #[from]
55        rocket_multipart::Error,
56    ),
57
58    /// DB pool/connection error.
59    #[error("DB error: {0}")]
60    DB(
61        #[doc(hidden)]
62        #[from]
63        sqlx::Error,
64    ),
65
66    /// DB migration error.
67    #[error("DB migration error: {0}")]
68    DBMigrate(
69        #[doc(hidden)]
70        #[from]
71        sqlx::migrate::MigrateError,
72    ),
73
74    /// Unexpected runtime error.
75    #[error("{0}")]
76    Runtime(#[doc(hidden)] Cow<'static, str>),
77
78    /// I/O error.
79    #[error("I/O error: {0}")]
80    IO(
81        #[doc(hidden)]
82        #[from]
83        io::Error,
84    ),
85
86    /// OpenSSL error.
87    #[error("OpenSSL error: {0}")]
88    OSSL(
89        #[doc(hidden)]
90        #[from]
91        openssl::error::ErrorStack,
92    ),
93
94    /// JOSE error.
95    #[error("JOSE error: {0}")]
96    JOSE(
97        #[doc(hidden)]
98        #[from]
99        josekit::JoseError,
100    ),
101
102    /// Rocket-fiendly handler error.
103    #[error("Rocket handler error ({status}): {info}")]
104    HTTP {
105        /// HTTP Status code.
106        status: Status,
107        /// Text message giving more context to the reason this error was raised.
108        info: Cow<'static, str>,
109    },
110}
111
112impl MyError {
113    /// Return a new instance that is an HTTP variant w/ the designated Status
114    /// code and the original error string.
115    pub fn with_status(self, s: Status) -> Self {
116        match self {
117            MyError::HTTP { status, info } => {
118                info!("Replace status {} w/ {}", status, s);
119                MyError::HTTP { status: s, info }
120            }
121            _ => MyError::HTTP {
122                status: s,
123                info: self.to_string().into(),
124            },
125        }
126    }
127}
128
129#[rocket::async_trait]
130impl<'r> Responder<'r, 'static> for MyError {
131    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
132        let status = match self {
133            MyError::HTTP { status, .. } => status,
134            _ => Status::InternalServerError,
135        };
136        error!("Failed: {}", &self);
137        Response::build_from(
138            json!({
139                "status": status.code,
140                "info": format!("{}", self),
141            })
142            .respond_to(req)?,
143        )
144        .status(status)
145        .ok()
146    }
147}