// // Error generator macro // use crate::db::models::EventType; use crate::http_client::CustomHttpClientError; use serde::ser::{Serialize, SerializeStruct, Serializer}; 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 )),+ } #[derive(Debug)] pub struct ErrorEvent { pub event: EventType } pub struct Error { message: String, error: ErrorKind, error_code: u16, event: Option } $(impl From<$ty> for Error { fn from(err: $ty) -> Self { Error::from((stringify!($name), err)) } })+ $(impl> 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, event: None } } })+ 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 handlebars::RenderError as HbErr; use jsonwebtoken::errors::Error as JwtErr; use lettre::address::AddressError as AddrErr; use lettre::error::Error as LettreErr; use lettre::transport::smtp::Error as SmtpErr; use opendal::Error as OpenDALErr; use openssl::error::ErrorStack as SSLErr; use regex::Error as RegexErr; use reqwest::Error as ReqErr; use rocket::error::Error as RocketErr; use serde_json::{Error as SerdeErr, Value}; use std::io::Error as IoErr; use std::time::SystemTimeError as TimeErr; use webauthn_rs::prelude::WebauthnError as WebauthnErr; use yubico::yubicoerror::YubicoError as YubiErr; #[derive(Serialize)] pub struct Empty {} pub struct Compact {} // 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 Empty(Empty): _no_source, _serialize, // Used to represent err! calls Simple(String): _no_source, _api_error, Compact(Compact): _no_source, _compact_api_error, // Used in our custom http client to handle non-global IPs and blocked domains CustomHttpClient(CustomHttpClientError): _has_source, _api_error, // Used for special return values, like 2FA errors Json(Value): _no_source, _serialize, Db(DieselErr): _has_source, _api_error, R2d2(R2d2Err): _has_source, _api_error, Serde(SerdeErr): _has_source, _api_error, JWt(JwtErr): _has_source, _api_error, Handlebars(HbErr): _has_source, _api_error, Io(IoErr): _has_source, _api_error, Time(TimeErr): _has_source, _api_error, Req(ReqErr): _has_source, _api_error, Regex(RegexErr): _has_source, _api_error, Yubico(YubiErr): _has_source, _api_error, Lettre(LettreErr): _has_source, _api_error, Address(AddrErr): _has_source, _api_error, Smtp(SmtpErr): _has_source, _api_error, OpenSSL(SSLErr): _has_source, _api_error, Rocket(RocketErr): _has_source, _api_error, DieselCon(DieselConErr): _has_source, _api_error, Webauthn(WebauthnErr): _has_source, _api_error, OpenDAL(OpenDALErr): _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::Empty(_) => Ok(()), ErrorKind::Simple(ref s) => { if &self.message == s { write!(f, "{}", self.message) } else { write!(f, "{}. {}", self.message, s) } } ErrorKind::Json(_) => write!(f, "{}", self.message), _ => unreachable!(), }, } } } impl Error { pub fn new, N: Into>(usr_msg: M, log_msg: N) -> Self { (usr_msg, log_msg.into()).into() } pub fn new_msg + Clone>(usr_msg: M) -> Self { (usr_msg.clone(), usr_msg.into()).into() } pub fn empty() -> Self { Empty {}.into() } #[must_use] pub fn with_msg>(mut self, msg: M) -> Self { self.message = msg.into(); self } #[must_use] pub fn with_kind(mut self, kind: ErrorKind) -> Self { self.error = kind; self } #[must_use] pub const fn with_code(mut self, code: u16) -> Self { self.error_code = code; self } #[must_use] pub fn with_event(mut self, event: ErrorEvent) -> Self { self.event = Some(event); self } pub fn get_event(&self) -> &Option { &self.event } pub fn message(&self) -> &str { &self.message } } pub trait MapResult { fn map_res(self, msg: &str) -> Result; } impl> MapResult for Result { fn map_res(self, msg: &str) -> Result { self.map_err(|e| e.into().with_msg(msg)) } } impl> MapResult<()> for Result { fn map_res(self, msg: &str) -> Result<(), Error> { self.and(Ok(())).map_res(msg) } } impl MapResult for Option { fn map_res(self, msg: &str) -> Result { self.ok_or_else(|| Error::new(msg, "")) } } const fn _has_source(e: T) -> Option { Some(e) } fn _no_source(_: T) -> Option { None } fn _serialize(e: &impl Serialize, _msg: &str) -> String { serde_json::to_string(e).unwrap() } /// This will serialize the default ApiErrorResponse /// It will add the needed fields which are mostly empty or have multiple copies of the message /// This is more efficient than having a larger struct and use the Serialize derive /// It also prevents using `json!()` calls to create the final output impl Serialize for ApiErrorResponse<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { #[derive(serde::Serialize)] struct ErrorModel<'a> { message: &'a str, object: &'static str, } let mut state = serializer.serialize_struct("ApiErrorResponse", 9)?; state.serialize_field("message", self.0.message)?; let mut validation_errors = std::collections::HashMap::with_capacity(1); validation_errors.insert("", vec![self.0.message]); state.serialize_field("validationErrors", &validation_errors)?; let error_model = ErrorModel { message: self.0.message, object: "error", }; state.serialize_field("errorModel", &error_model)?; state.serialize_field("error", "")?; state.serialize_field("error_description", "")?; state.serialize_field("exceptionMessage", &None::<()>)?; state.serialize_field("exceptionStackTrace", &None::<()>)?; state.serialize_field("innerExceptionMessage", &None::<()>)?; state.serialize_field("object", "error")?; state.end() } } /// This will serialize the smaller CompactApiErrorResponse /// It will add the needed fields which are mostly empty /// This is more efficient than having a larger struct and use the Serialize derive /// It also prevents using `json!()` calls to create the final output impl Serialize for CompactApiErrorResponse<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("CompactApiErrorResponse", 6)?; state.serialize_field("message", self.0.message)?; state.serialize_field("validationErrors", &None::<()>)?; state.serialize_field("exceptionMessage", &None::<()>)?; state.serialize_field("exceptionStackTrace", &None::<()>)?; state.serialize_field("innerExceptionMessage", &None::<()>)?; state.serialize_field("object", "error")?; state.end() } } /// Main API Error struct template /// This struct which we can be used by both ApiErrorResponse and CompactApiErrorResponse /// is small and doesn't contain unneeded empty fields. This is more memory efficient, but also less code to compile struct ApiErrorMsg<'a> { message: &'a str, } /// Default API Error response struct /// The custom serialization adds all other needed fields struct ApiErrorResponse<'a>(ApiErrorMsg<'a>); /// Compact API Error response struct used for some newer error responses /// The custom serialization adds all other needed fields struct CompactApiErrorResponse<'a>(ApiErrorMsg<'a>); fn _api_error(_: &impl std::any::Any, msg: &str) -> String { let response = ApiErrorMsg { message: msg, }; serde_json::to_string(&ApiErrorResponse(response)).unwrap() } fn _compact_api_error(_: &impl std::any::Any, msg: &str) -> String { let response = ApiErrorMsg { message: msg, }; serde_json::to_string(&CompactApiErrorResponse(response)).unwrap() } // // Rocket responder impl // use std::io::Cursor; use rocket::http::{ContentType, Status}; use rocket::request::Request; use rocket::response::{self, Responder, Response}; impl Responder<'_, 'static> for Error { fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { match self.error { ErrorKind::Empty(_) | ErrorKind::Simple(_) | ErrorKind::Compact(_) => {} // Don't print the error in this situation _ => error!(target: "error", "{self:#?}"), }; let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest); let body = self.to_string(); Response::build().status(code).header(ContentType::JSON).sized_body(Some(body.len()), Cursor::new(body)).ok() } } // // Error return macros // #[macro_export] macro_rules! err { ($kind:ident, $msg:expr) => {{ let msg = $msg; error!("{msg}"); return Err($crate::error::Error::new_msg(msg).with_kind($crate::error::ErrorKind::$kind($crate::error::$kind {}))); }}; ($msg:expr) => {{ let msg = $msg; error!("{msg}"); return Err($crate::error::Error::new_msg(msg)); }}; ($msg:expr, ErrorEvent $err_event:tt) => {{ let msg = $msg; error!("{msg}"); return Err($crate::error::Error::new_msg(msg).with_event($crate::error::ErrorEvent $err_event)); }}; ($usr_msg:expr, $log_value:expr) => {{ let usr_msg = $usr_msg; let log_value = $log_value; error!("{usr_msg}. {log_value}"); return Err($crate::error::Error::new(usr_msg, log_value)); }}; ($usr_msg:expr, $log_value:expr, ErrorEvent $err_event:tt) => {{ let usr_msg = $usr_msg; let log_value = $log_value; error!("{usr_msg}. {log_value}"); return Err($crate::error::Error::new(usr_msg, log_value).with_event($crate::error::ErrorEvent $err_event)); }}; } #[macro_export] macro_rules! err_silent { ($msg:expr) => {{ return Err($crate::error::Error::new_msg($msg)); }}; ($msg:expr, ErrorEvent $err_event:tt) => {{ return Err($crate::error::Error::new_msg($msg).with_event($crate::error::ErrorEvent $err_event)); }}; ($usr_msg:expr, $log_value:expr) => {{ return Err($crate::error::Error::new($usr_msg, $log_value)); }}; ($usr_msg:expr, $log_value:expr, ErrorEvent $err_event:tt) => {{ return Err($crate::error::Error::new($usr_msg, $log_value).with_event($crate::error::ErrorEvent $err_event)); }}; } #[macro_export] macro_rules! err_code { ($msg:expr, $err_code:expr) => {{ let msg = $msg; error!("{msg}"); return Err($crate::error::Error::new_msg(msg).with_code($err_code)); }}; ($usr_msg:expr, $log_value:expr, $err_code:expr) => {{ let usr_msg = $usr_msg; let log_value = $log_value; 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()); }}; ($expr:expr, $log_value:expr, $err_event:expr, ErrorEvent) => {{ return Err(($log_value, $expr).into().with_event($err_event)); }}; } #[macro_export] macro_rules! err_handler { ($expr:expr) => {{ error!(target: "auth", "Unauthorized Error: {}", $expr); return ::rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, $expr)); }}; ($usr_msg:expr, $log_value:expr) => {{ let usr_msg = $usr_msg; let log_value = $log_value; error!(target: "auth", "Unauthorized Error: {usr_msg}. {log_value}"); return ::rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, usr_msg)); }}; }