]> Untitled Git - lemmy.git/blob - crates/utils/src/lib.rs
Rework error handling (fixes #1714) (#2135)
[lemmy.git] / crates / utils / src / lib.rs
1 #[macro_use]
2 extern crate strum_macros;
3 #[macro_use]
4 extern crate smart_default;
5
6 pub mod apub;
7 pub mod email;
8 pub mod rate_limit;
9 pub mod request;
10 pub mod settings;
11
12 pub mod claims;
13 #[cfg(test)]
14 mod test;
15 pub mod utils;
16 pub mod version;
17
18 mod sensitive;
19
20 pub use sensitive::Sensitive;
21
22 use actix_web::HttpResponse;
23 use http::StatusCode;
24 use std::{fmt, fmt::Display};
25 use tracing_error::SpanTrace;
26
27 pub type ConnectionId = usize;
28
29 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
30 pub struct IpAddr(pub String);
31
32 impl fmt::Display for IpAddr {
33   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34     write!(f, "{}", self.0)
35   }
36 }
37
38 #[macro_export]
39 macro_rules! location_info {
40   () => {
41     format!(
42       "None value at {}:{}, column {}",
43       file!(),
44       line!(),
45       column!()
46     )
47   };
48 }
49
50 #[derive(serde::Serialize)]
51 struct ApiError {
52   error: &'static str,
53 }
54
55 pub struct LemmyError {
56   pub message: Option<&'static str>,
57   pub inner: anyhow::Error,
58   pub context: SpanTrace,
59 }
60
61 impl LemmyError {
62   /// Create LemmyError from a message, including stack trace
63   pub fn from_message(message: &'static str) -> Self {
64     let inner = anyhow::anyhow!("{}", message);
65     LemmyError {
66       message: Some(message),
67       inner,
68       context: SpanTrace::capture(),
69     }
70   }
71
72   /// Create a LemmyError from error and message, including stack trace
73   pub fn from_error_message<E>(error: E, message: &'static str) -> Self
74   where
75     E: Into<anyhow::Error>,
76   {
77     LemmyError {
78       message: Some(message),
79       inner: error.into(),
80       context: SpanTrace::capture(),
81     }
82   }
83
84   /// Add message to existing LemmyError (or overwrite existing error)
85   pub fn with_message(self, message: &'static str) -> Self {
86     LemmyError {
87       message: Some(message),
88       ..self
89     }
90   }
91
92   pub fn to_json(&self) -> Result<String, Self> {
93     let api_error = match self.message {
94       Some(error) => ApiError { error },
95       None => ApiError { error: "Unknown" },
96     };
97
98     Ok(serde_json::to_string(&api_error)?)
99   }
100 }
101
102 impl<T> From<T> for LemmyError
103 where
104   T: Into<anyhow::Error>,
105 {
106   fn from(t: T) -> Self {
107     LemmyError {
108       message: None,
109       inner: t.into(),
110       context: SpanTrace::capture(),
111     }
112   }
113 }
114
115 impl std::fmt::Debug for LemmyError {
116   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117     f.debug_struct("LemmyError")
118       .field("message", &self.message)
119       .field("inner", &self.inner)
120       .field("context", &"SpanTrace")
121       .finish()
122   }
123 }
124
125 impl Display for LemmyError {
126   fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
127     if let Some(message) = self.message {
128       write!(f, "{}: ", message)?;
129     }
130     writeln!(f, "{}", self.inner)?;
131     self.context.fmt(f)
132   }
133 }
134
135 impl actix_web::error::ResponseError for LemmyError {
136   fn status_code(&self) -> StatusCode {
137     match self.inner.downcast_ref::<diesel::result::Error>() {
138       Some(diesel::result::Error::NotFound) => StatusCode::NOT_FOUND,
139       _ => StatusCode::BAD_REQUEST,
140     }
141   }
142
143   fn error_response(&self) -> HttpResponse {
144     if let Some(message) = &self.message {
145       HttpResponse::build(self.status_code()).json(ApiError { error: message })
146     } else {
147       HttpResponse::build(self.status_code())
148         .content_type("text/plain")
149         .body(self.inner.to_string())
150     }
151   }
152 }