error.rs (10809B)
1 // 2 // Error generator macro 3 // 4 use core::error::Error as StdError; 5 use core::fmt::{self, Debug, Display, Formatter}; 6 7 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 8 macro_rules! make_error { 9 ( $( $name:ident ( $ty:ty ): $src_fn:expr, $usr_msg_fun:expr ),+ $(,)? ) => { 10 const BAD_REQUEST: u16 = 400; 11 enum ErrorKind { $($name( $ty )),+ } 12 #[allow(clippy::error_impl_error)] 13 pub struct Error { message: String, error: ErrorKind, error_code: u16 } 14 15 $(impl From<$ty> for Error { 16 fn from(err: $ty) -> Self { Error::from((stringify!($name), err)) } 17 })+ 18 $(impl<S: Into<String>> From<(S, $ty)> for Error { 19 fn from(val: (S, $ty)) -> Self { 20 Error { message: val.0.into(), error: ErrorKind::$name(val.1), error_code: BAD_REQUEST } 21 } 22 })+ 23 impl StdError for Error { 24 fn source(&self) -> Option<&(dyn StdError + 'static)> { 25 match self.error {$( ErrorKind::$name(ref e) => $src_fn(e), )+} 26 } 27 } 28 impl Display for Error { 29 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 30 match self.error {$( 31 ErrorKind::$name(ref e) => f.write_str(&$usr_msg_fun(e, &self.message)), 32 )+} 33 } 34 } 35 }; 36 } 37 use core::any::Any; 38 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] 39 use core::convert::Infallible; 40 use diesel::r2d2::PoolError as R2d2Err; 41 use diesel::result::Error as DieselErr; 42 use diesel::ConnectionError as DieselConErr; 43 use jsonwebtoken::errors::Error as JwtErr; 44 use openssl::error::ErrorStack as SSLErr; 45 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] 46 use priv_sep::UnveilErr; 47 use rocket::error::Error as RocketErr; 48 use serde_json::{Error as SerdeErr, Value}; 49 use std::io::Error as IoErr; 50 use std::time::SystemTimeError as TimeErr; 51 use webauthn_rs::prelude::WebauthnError as WebauthnErr; 52 53 #[derive(Serialize)] 54 struct Empty; 55 56 // Error struct 57 // Contains a String error message, meant for the user and an enum variant, with an error of different types. 58 // 59 // After the variant itself, there are two expressions. The first one indicates whether the error contains a source error (that we pretty print). 60 // The second one contains the function used to obtain the response sent to the client 61 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] 62 make_error! { 63 // Just an empty error 64 Empty(Empty): _no_source, _serialize, 65 // Used to represent err! calls 66 Simple(String): _no_source, _api_error, 67 // Used for special return values, like 2FA errors 68 Json(Value): _no_source, _serialize, 69 Db(DieselErr): _has_source, _api_error, 70 R2d2(R2d2Err): _has_source, _api_error, 71 Serde(SerdeErr): _has_source, _api_error, 72 JWt(JwtErr): _has_source, _api_error, 73 74 Io(IoErr): _has_source, _api_error, 75 Time(TimeErr): _has_source, _api_error, 76 77 OpenSSL(SSLErr): _has_source, _api_error, 78 Rocket(RocketErr): _has_source, _api_error, 79 80 DieselCon(DieselConErr): _has_source, _api_error, 81 Webauthn(WebauthnErr): _has_source, _api_error, 82 } 83 // Error struct 84 // Contains a String error message, meant for the user and an enum variant, with an error of different types. 85 // 86 // After the variant itself, there are two expressions. The first one indicates whether the error contains a source error (that we pretty print). 87 // The second one contains the function used to obtain the response sent to the client 88 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] 89 make_error! { 90 // Just an empty error 91 Empty(Empty): _no_source, _serialize, 92 // Used to represent err! calls 93 Simple(String): _no_source, _api_error, 94 // Used for special return values, like 2FA errors 95 Json(Value): _no_source, _serialize, 96 Db(DieselErr): _has_source, _api_error, 97 R2d2(R2d2Err): _has_source, _api_error, 98 Serde(SerdeErr): _has_source, _api_error, 99 JWt(JwtErr): _has_source, _api_error, 100 101 Io(IoErr): _has_source, _api_error, 102 Time(TimeErr): _has_source, _api_error, 103 104 OpenSSL(SSLErr): _has_source, _api_error, 105 Rocket(RocketErr): _has_source, _api_error, 106 Unveil(UnveilErr): _has_source, _api_error, 107 108 DieselCon(DieselConErr): _has_source, _api_error, 109 Webauthn(WebauthnErr): _has_source, _api_error, 110 } 111 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))] 112 impl From<Infallible> for Error { 113 #[inline] 114 fn from(value: Infallible) -> Self { 115 match value {} 116 } 117 } 118 impl Debug for Error { 119 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 120 match self.source() { 121 Some(e) => write!(f, "{}.\n[CAUSE] {:#?}", self.message, e), 122 None => match self.error { 123 ErrorKind::Empty(_) => Ok(()), 124 ErrorKind::Simple(ref s) => { 125 if &self.message == s { 126 write!(f, "{}", self.message) 127 } else { 128 write!(f, "{}. {}", self.message, s) 129 } 130 } 131 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] 132 ErrorKind::Unveil(ref err) => Display::fmt(err, f), 133 ErrorKind::Json(_) => write!(f, "{}", self.message), 134 ErrorKind::Db(_) 135 | ErrorKind::R2d2(_) 136 | ErrorKind::Serde(_) 137 | ErrorKind::JWt(_) 138 | ErrorKind::Io(_) 139 | ErrorKind::Time(_) 140 | ErrorKind::OpenSSL(_) 141 | ErrorKind::Rocket(_) 142 | ErrorKind::DieselCon(_) 143 | ErrorKind::Webauthn(_) => unreachable!(), 144 }, 145 } 146 } 147 } 148 149 impl Error { 150 pub fn new<M: Into<String>, N: Into<String>>(usr_msg: M, log_msg: N) -> Self { 151 (usr_msg, log_msg.into()).into() 152 } 153 #[must_use] 154 pub fn empty() -> Self { 155 Empty {}.into() 156 } 157 #[must_use] 158 fn with_msg<M: Into<String>>(mut self, msg: M) -> Self { 159 self.message = msg.into(); 160 self 161 } 162 #[must_use] 163 pub const fn with_code(mut self, code: u16) -> Self { 164 self.error_code = code; 165 self 166 } 167 } 168 169 pub trait MapResult<S> { 170 fn map_res(self, msg: &str) -> Result<S, Error>; 171 } 172 173 impl<S, E: Into<Error>> MapResult<S> for Result<S, E> { 174 fn map_res(self, msg: &str) -> Result<S, Error> { 175 self.map_err(|e| e.into().with_msg(msg)) 176 } 177 } 178 179 impl<E: Into<Error>> MapResult<()> for Result<usize, E> { 180 fn map_res(self, msg: &str) -> Result<(), Error> { 181 self.and(Ok(())).map_res(msg) 182 } 183 } 184 185 impl<S> MapResult<S> for Option<S> { 186 fn map_res(self, msg: &str) -> Result<S, Error> { 187 self.ok_or_else(|| Error::new(msg, "")) 188 } 189 } 190 #[allow(clippy::unnecessary_wraps)] 191 const fn _has_source<T>(e: T) -> Option<T> { 192 Some(e) 193 } 194 fn _no_source<T, S>(_: T) -> Option<S> { 195 None 196 } 197 198 fn _serialize(e: &impl serde::Serialize, _msg: &str) -> String { 199 serde_json::to_string(e).unwrap() 200 } 201 202 fn _api_error(_: &impl Any, msg: &str) -> String { 203 let json = json!({ 204 "Message": msg, 205 "error": "", 206 "error_description": "", 207 "ValidationErrors": {"": [ msg ]}, 208 "ErrorModel": { 209 "Message": msg, 210 "Object": "error" 211 }, 212 "ExceptionMessage": null, 213 "ExceptionStackTrace": null, 214 "InnerExceptionMessage": null, 215 "Object": "error" 216 }); 217 _serialize(&json, "") 218 } 219 220 // 221 // Rocket responder impl 222 // 223 use rocket::http::{ContentType, Status}; 224 use rocket::request::Request; 225 use rocket::response::{self, Responder, Response}; 226 use std::io::Cursor; 227 228 impl<'r> Responder<'r, 'static> for Error { 229 fn respond_to(self, _: &Request<'_>) -> response::Result<'static> { 230 let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest); 231 let body = self.to_string(); 232 Response::build() 233 .status(code) 234 .header(ContentType::JSON) 235 .sized_body(Some(body.len()), Cursor::new(body)) 236 .ok() 237 } 238 } 239 240 // 241 // Error return macros 242 // 243 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 244 #[macro_export] 245 macro_rules! err { 246 ($msg:expr) => {{ 247 return Err($crate::error::Error::new($msg, $msg)); 248 }}; 249 ($msg:expr, ErrorEvent $err_event:tt) => {{ 250 return Err($crate::error::Error::new($msg, $msg).with_event($crate::error::ErrorEvent $err_event)); 251 }}; 252 ($usr_msg:expr, $log_value:expr) => {{ 253 return Err($crate::error::Error::new($usr_msg, $log_value)); 254 }}; 255 ($usr_msg:expr, $log_value:expr, ErrorEvent $err_event:tt) => {{ 256 return Err($crate::error::Error::new($usr_msg, $log_value).with_event($crate::error::ErrorEvent $err_event)); 257 }}; 258 } 259 260 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 261 #[macro_export] 262 macro_rules! err_silent { 263 ($msg:expr) => {{ 264 return Err($crate::error::Error::new($msg, $msg)); 265 }}; 266 ($usr_msg:expr, $log_value:expr) => {{ 267 return Err($crate::error::Error::new($usr_msg, $log_value)); 268 }}; 269 } 270 271 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 272 #[macro_export] 273 macro_rules! err_code { 274 ($msg:expr, $err_code:expr) => {{ 275 return Err($crate::error::Error::new($msg, $msg).with_code($err_code)); 276 }}; 277 ($usr_msg:expr, $log_value:expr, $err_code:expr) => {{ 278 return Err($crate::error::Error::new($usr_msg, $log_value).with_code($err_code)); 279 }}; 280 } 281 282 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 283 #[macro_export] 284 macro_rules! err_discard { 285 ($msg:expr, $data:expr) => {{ 286 std::io::copy(&mut $data.open(), &mut std::io::sink()).ok(); 287 return Err($crate::error::Error::new($msg, $msg)); 288 }}; 289 ($usr_msg:expr, $log_value:expr, $data:expr) => {{ 290 std::io::copy(&mut $data.open(), &mut std::io::sink()).ok(); 291 return Err($crate::error::Error::new($usr_msg, $log_value)); 292 }}; 293 } 294 295 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 296 #[macro_export] 297 macro_rules! err_json { 298 ($expr:expr, $log_value:expr) => {{ 299 return Err(($log_value, $expr).into()); 300 }}; 301 ($expr:expr, $log_value:expr, $err_event:expr, ErrorEvent) => {{ 302 return Err(($log_value, $expr).into().with_event($err_event)); 303 }}; 304 } 305 306 #[expect(edition_2024_expr_fragment_specifier, reason = "false positive")] 307 #[macro_export] 308 macro_rules! err_handler { 309 ($expr:expr) => {{ 310 return ::rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, $expr)); 311 }}; 312 ($usr_msg:expr, $log_value:expr) => {{ 313 return ::rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, $usr_msg)); 314 }}; 315 }