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