aboutsummaryrefslogtreecommitdiff
path: root/src/error.rs
blob: 9c597a8b9f9288bd1414ff16f0699b51e6c88a9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//
// Error generator macro
//
use std::error::Error as StdError;

macro_rules! make_error {
    ( $( $name:ident ( $ty:ty ): $src_fn:expr, $usr_msg_fun:expr ),+ $(,)? ) => {
        const BAD_REQUEST: u16 = 400;

        pub enum ErrorKind { $($name( $ty )),+ }
        pub struct Error { message: String, error: ErrorKind, error_code: u16 }

        $(impl From<$ty> for Error {
            fn from(err: $ty) -> Self { Error::from((stringify!($name), err)) }
        })+
        $(impl<S: Into<String>> From<(S, $ty)> for Error {
            fn from(val: (S, $ty)) -> Self {
                Error { message: val.0.into(), error: ErrorKind::$name(val.1), error_code: BAD_REQUEST }
            }
        })+
        impl StdError for Error {
            fn source(&self) -> Option<&(dyn StdError + 'static)> {
                match &self.error {$( ErrorKind::$name(e) => $src_fn(e), )+}
            }
        }
        impl std::fmt::Display for Error {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                match &self.error {$(
                   ErrorKind::$name(e) => f.write_str(&$usr_msg_fun(e, &self.message)),
                )+}
            }
        }
    };
}

use diesel::r2d2::PoolError as R2d2Err;
use diesel::result::Error as DieselErr;
use diesel::ConnectionError as DieselConErr;
use diesel_migrations::RunMigrationsError as DieselMigErr;
use handlebars::RenderError as HbErr;
use jsonwebtoken::errors::Error as JwtErr;
use regex::Error as RegexErr;
use reqwest::Error as ReqErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;

use std::time::SystemTimeError as TimeErr;
use u2f::u2ferror::U2fError as U2fErr;
use yubico::yubicoerror::YubicoError as YubiErr;

use lettre::address::AddressError as AddrErr;
use lettre::error::Error as LettreErr;
use lettre::message::mime::FromStrError as FromStrErr;
use lettre::transport::smtp::Error as SmtpErr;

#[derive(Serialize)]
pub struct Empty {}

// Error struct
// Contains a String error message, meant for the user and an enum variant, with an error of different types.
//
// After the variant itself, there are two expressions. The first one indicates whether the error contains a source error (that we pretty print).
// The second one contains the function used to obtain the response sent to the client
make_error! {
    // Just an empty error
    EmptyError(Empty):     _no_source, _serialize,
    // Used to represent err! calls
    SimpleError(String):  _no_source,  _api_error,
    // Used for special return values, like 2FA errors
    JsonError(Value):     _no_source,  _serialize,
    DbError(DieselErr):   _has_source, _api_error,
    R2d2Error(R2d2Err):   _has_source, _api_error,
    U2fError(U2fErr):     _has_source, _api_error,
    SerdeError(SerdeErr): _has_source, _api_error,
    JWtError(JwtErr):     _has_source, _api_error,
    TemplError(HbErr):    _has_source, _api_error,
    //WsError(ws::Error): _has_source, _api_error,
    IoError(IoErr):       _has_source, _api_error,
    TimeError(TimeErr):   _has_source, _api_error,
    ReqError(ReqErr):     _has_source, _api_error,
    RegexError(RegexErr): _has_source, _api_error,
    YubiError(YubiErr):   _has_source, _api_error,

    LettreError(LettreErr):   _has_source, _api_error,
    AddressError(AddrErr):    _has_source, _api_error,
    SmtpError(SmtpErr):       _has_source, _api_error,
    FromStrError(FromStrErr): _has_source, _api_error,

    DieselConError(DieselConErr): _has_source, _api_error,
    DieselMigError(DieselMigErr): _has_source, _api_error,
}

impl std::fmt::Debug for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self.source() {
            Some(e) => write!(f, "{}.\n[CAUSE] {:#?}", self.message, e),
            None => match self.error {
                ErrorKind::EmptyError(_) => Ok(()),
                ErrorKind::SimpleError(ref s) => {
                    if &self.message == s {
                        write!(f, "{}", self.message)
                    } else {
                        write!(f, "{}. {}", self.message, s)
                    }
                }
                ErrorKind::JsonError(_) => write!(f, "{}", self.message),
                _ => unreachable!(),
            },
        }
    }
}

impl Error {
    pub fn new<M: Into<String>, N: Into<String>>(usr_msg: M, log_msg: N) -> Self {
        (usr_msg, log_msg.into()).into()
    }

    pub fn empty() -> Self {
        Empty {}.into()
    }

    pub fn with_msg<M: Into<String>>(mut self, msg: M) -> Self {
        self.message = msg.into();
        self
    }

    pub const fn with_code(mut self, code: u16) -> Self {
        self.error_code = code;
        self
    }
}

pub trait MapResult<S> {
    fn map_res(self, msg: &str) -> Result<S, Error>;
}

impl<S, E: Into<Error>> MapResult<S> for Result<S, E> {
    fn map_res(self, msg: &str) -> Result<S, Error> {
        self.map_err(|e| e.into().with_msg(msg))
    }
}

impl<E: Into<Error>> MapResult<()> for Result<usize, E> {
    fn map_res(self, msg: &str) -> Result<(), Error> {
        self.and(Ok(())).map_res(msg)
    }
}

impl<S> MapResult<S> for Option<S> {
    fn map_res(self, msg: &str) -> Result<S, Error> {
        self.ok_or_else(|| Error::new(msg, ""))
    }
}

#[allow(clippy::unnecessary_wraps)]
const fn _has_source<T>(e: T) -> Option<T> {
    Some(e)
}
fn _no_source<T, S>(_: T) -> Option<S> {
    None
}

fn _serialize(e: &impl serde::Serialize, _msg: &str) -> String {
    serde_json::to_string(e).unwrap()
}

fn _api_error(_: &impl std::any::Any, msg: &str) -> String {
    let json = json!({
        "Message": "",
        "error": "",
        "error_description": "",
        "ValidationErrors": {"": [ msg ]},
        "ErrorModel": {
            "Message": msg,
            "Object": "error"
        },
        "Object": "error"
    });
    _serialize(&json, "")
}

//
// Rocket responder impl
//
use std::io::Cursor;

use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{self, Responder, Response};

impl<'r> Responder<'r> for Error {
    fn respond_to(self, _: &Request) -> response::Result<'r> {
        match self.error {
            ErrorKind::EmptyError(_) => {}  // Don't print the error in this situation
            ErrorKind::SimpleError(_) => {} // Don't print the error in this situation
            _ => error!(target: "error", "{:#?}", self),
        };

        let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest);

        Response::build()
            .status(code)
            .header(ContentType::JSON)
            .sized_body(Cursor::new(format!("{}", self)))
            .ok()
    }
}

//
// Error return macros
//
#[macro_export]
macro_rules! err {
    ($msg:expr) => {{
        error!("{}", $msg);
        return Err(crate::error::Error::new($msg, $msg));
    }};
    ($usr_msg:expr, $log_value:expr) => {{
        error!("{}. {}", $usr_msg, $log_value);
        return Err(crate::error::Error::new($usr_msg, $log_value));
    }};
}

#[macro_export]
macro_rules! err_code {
    ($msg:expr, $err_code: literal) => {{
        error!("{}", $msg);
        return Err(crate::error::Error::new($msg, $msg).with_code($err_code));
    }};
    ($usr_msg:expr, $log_value:expr, $err_code: literal) => {{
        error!("{}. {}", $usr_msg, $log_value);
        return Err(crate::error::Error::new($usr_msg, $log_value).with_code($err_code));
    }};
}

#[macro_export]
macro_rules! err_discard {
    ($msg:expr, $data:expr) => {{
        std::io::copy(&mut $data.open(), &mut std::io::sink()).ok();
        return Err(crate::error::Error::new($msg, $msg));
    }};
    ($usr_msg:expr, $log_value:expr, $data:expr) => {{
        std::io::copy(&mut $data.open(), &mut std::io::sink()).ok();
        return Err(crate::error::Error::new($usr_msg, $log_value));
    }};
}

#[macro_export]
macro_rules! err_json {
    ($expr:expr, $log_value:expr) => {{
        return Err(($log_value, $expr).into());
    }};
}

#[macro_export]
macro_rules! err_handler {
    ($expr:expr) => {{
        error!(target: "auth", "Unauthorized Error: {}", $expr);
        return ::rocket::request::Outcome::Failure((rocket::http::Status::Unauthorized, $expr));
    }};
    ($usr_msg:expr, $log_value:expr) => {{
        error!(target: "auth", "Unauthorized Error: {}. {}", $usr_msg, $log_value);
        return ::rocket::request::Outcome::Failure((rocket::http::Status::Unauthorized, $usr_msg));
    }};
}