1use std::{collections::HashMap, fmt, sync::Arc};
2
3use error_info::ErrorInfo;
4use http::StatusCode;
5use tracing_error::SpanTrace;
6
7pub type Result<T, E = Box<Error>> = std::result::Result<T, E>;
8
9#[derive(Clone, Copy, ErrorInfo)]
11pub enum GenericErrorCode {
12 #[error(status = StatusCode::BAD_REQUEST, message = "The request is not well formed")]
13 BadRequest,
14 #[error(status = StatusCode::UNAUTHORIZED, message = "Not authorized to access this resource")]
15 Unauthorized,
16 #[error(status = StatusCode::FORBIDDEN, message = "Forbidden access to the resource")]
17 Forbidden,
18 #[error(status = StatusCode::NOT_FOUND, message = "The resource could not be found")]
19 NotFound,
20 #[error(status = StatusCode::GATEWAY_TIMEOUT, message = "Timeout exceeded while waiting for a response")]
21 GatewayTimeout,
22 #[error(status = StatusCode::INTERNAL_SERVER_ERROR, message = "Internal server error")]
23 InternalServerError,
24}
25
26#[derive(Clone)]
28pub struct Error {
29 pub(super) info: Arc<dyn ErrorInfo + Send + Sync + 'static>,
30 pub(super) reason: Option<String>,
31 pub(super) properties: Option<HashMap<String, serde_json::Value>>,
32 pub(super) unexpected: bool,
33 pub(super) source: Option<Arc<dyn fmt::Display + Send + Sync>>,
34 pub(super) context: SpanTrace,
35}
36struct ErrorInfoDebug {
37 status: StatusCode,
38 code: &'static str,
39 raw_message: &'static str,
40 fields: HashMap<String, String>,
41}
42impl fmt::Debug for ErrorInfoDebug {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.debug_struct("ErrorInfo")
45 .field("status", &self.status)
46 .field("code", &self.code)
47 .field("raw_message", &self.raw_message)
48 .field("fields", &self.fields)
49 .finish()
50 }
51}
52impl fmt::Debug for Error {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.debug_struct("Error")
55 .field(
56 "info",
57 &ErrorInfoDebug {
58 status: self.info.status(),
59 code: self.info.code(),
60 raw_message: self.info.raw_message(),
61 fields: self.info.fields(),
62 },
63 )
64 .field("reason", &self.reason)
65 .field("properties", &self.properties)
66 .field("source", &self.source.as_ref().map(|s| s.to_string()))
67 .field("context", &self.context)
68 .finish()
69 }
70}
71impl Error {
72 pub fn new(info: impl ErrorInfo + Send + Sync + 'static) -> Box<Self> {
74 let info = Arc::new(info);
75 Box::new(Self {
76 unexpected: info.status().is_server_error(),
77 info,
78 reason: None,
79 properties: None,
80 source: None,
81 context: SpanTrace::capture(),
82 })
83 }
84
85 pub fn internal(reason: impl Into<String>) -> Box<Self> {
87 Self::new(GenericErrorCode::InternalServerError).with_reason(reason)
88 }
89
90 pub fn unexpected(mut self: Box<Self>) -> Box<Self> {
92 self.unexpected = true;
93 self
94 }
95
96 pub fn expected(mut self: Box<Self>) -> Box<Self> {
98 self.unexpected = false;
99 self
100 }
101
102 pub fn with_unexpected(mut self: Box<Self>, unexpected: bool) -> Box<Self> {
104 self.unexpected = unexpected;
105 self
106 }
107
108 pub fn with_reason(mut self: Box<Self>, reason: impl Into<String>) -> Box<Self> {
110 self.reason = Some(reason.into());
111 self
112 }
113
114 pub fn with_source<S: fmt::Display + Send + Sync + 'static>(mut self: Box<Self>, source: S) -> Box<Self> {
116 self.source = Some(Arc::new(source));
117 self
118 }
119
120 pub fn with_str_property(mut self: Box<Self>, key: &str, value: impl Into<String>) -> Box<Self> {
122 self.properties
123 .get_or_insert_with(HashMap::new)
124 .insert(key.to_string(), serde_json::Value::String(value.into()));
125 self
126 }
127
128 pub fn with_property(mut self: Box<Self>, key: &str, value: serde_json::Value) -> Box<Self> {
130 self.properties
131 .get_or_insert_with(HashMap::new)
132 .insert(key.to_string(), value);
133 self
134 }
135
136 pub fn info(&self) -> &dyn ErrorInfo {
138 self.info.as_ref()
139 }
140
141 pub fn is_unexpected(&self) -> bool {
143 self.unexpected
144 }
145
146 pub fn reason(&self) -> Option<&str> {
148 self.reason.as_deref()
149 }
150
151 pub fn properties(&self) -> Option<&HashMap<String, serde_json::Value>> {
153 self.properties.as_ref()
154 }
155
156 pub(super) fn reason_or_message(&self) -> String {
158 self.reason.clone().unwrap_or(self.info.message())
159 }
160}
161
162impl fmt::Display for Error {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 let status = self.info.status();
165 write!(
166 f,
167 "[{} {}] {}: {}",
168 status.as_str(),
169 status.canonical_reason().unwrap_or("Unknown"),
170 self.info.code(),
171 self.reason_or_message()
172 )?;
173 if f.alternate() {
174 if let Some(source) = &self.source {
175 write!(f, "\nCaused by: {source}")?;
176 }
177 write!(f, "\n{}", self.context)
178 } else {
179 Ok(())
180 }
181 }
182}
183
184#[macro_export]
201macro_rules! err (
202 ($reason:literal) => {
203 $crate::error::Error::internal($reason)
204 };
205 ($reason:literal,) => {
206 $crate::error::Error::internal($reason)
207 };
208 ($reason:literal, $($arg:tt)+) => {
209 $crate::error::Error::internal(format!($reason, $($arg)+))
210 };
211 ($info:expr) => {
212 $crate::error::Error::new($info)
213 };
214 ($info:expr, $reason:literal) => {
215 $crate::error::Error::new($info).with_reason($reason)
216 };
217 ($info:expr, $reason:literal,) => {
218 $crate::error::Error::new($info).with_reason($reason)
219 };
220 ($info:expr, $reason:literal, $($arg:tt)+) => {
221 $crate::error::Error::new($info).with_reason(format!($reason, $($arg)+))
222 };
223);
224pub(crate) use err;
225
226pub trait MapToErr<T> {
228 fn map_to_internal_err(self, reason: &'static str) -> Result<T>;
230 fn map_to_err(self, code: impl ErrorInfo + Send + Sync + 'static) -> Result<T>;
232 fn map_to_err_with(self, code: impl ErrorInfo + Send + Sync + 'static, reason: &'static str) -> Result<T>;
234}
235impl<T, E: fmt::Display + Send + Sync + 'static> MapToErr<T> for Result<T, E> {
236 fn map_to_internal_err(self, reason: &'static str) -> Result<T> {
237 self.map_err(|source| Error::internal(reason).with_source(source))
238 }
239
240 fn map_to_err(self, code: impl ErrorInfo + Send + Sync + 'static) -> Result<T> {
241 self.map_err(|source| Error::new(code).with_source(source))
242 }
243
244 fn map_to_err_with(self, code: impl ErrorInfo + Send + Sync + 'static, reason: &'static str) -> Result<T> {
245 self.map_err(|source| Error::new(code).with_reason(reason).with_source(source))
246 }
247}
248
249pub trait OkOrErr<T> {
251 fn ok_or_internal_err(self, reason: &'static str) -> Result<T>;
253 fn ok_or_err(self, code: impl ErrorInfo + Send + Sync + 'static) -> Result<T>;
255 fn ok_or_err_with(self, code: impl ErrorInfo + Send + Sync + 'static, reason: &'static str) -> Result<T>;
257}
258impl<T> OkOrErr<T> for Option<T> {
259 fn ok_or_internal_err(self, reason: &'static str) -> Result<T> {
260 self.ok_or_else(|| Error::internal(reason))
261 }
262
263 fn ok_or_err(self, code: impl ErrorInfo + Send + Sync + 'static) -> Result<T> {
264 self.ok_or_else(|| Error::new(code))
265 }
266
267 fn ok_or_err_with(self, code: impl ErrorInfo + Send + Sync + 'static, reason: &'static str) -> Result<T> {
268 self.ok_or_else(|| Error::new(code).with_reason(reason))
269 }
270}
271
272pub trait ResultExt {
274 fn unexpected(self) -> Self;
276 fn expected(self) -> Self;
278 fn with_str_property(self, key: &'static str, value: &'static str) -> Self;
280}
281impl<T> ResultExt for Result<T> {
282 fn unexpected(self) -> Self {
283 self.map_err(|err| err.unexpected())
284 }
285
286 fn expected(self) -> Self {
287 self.map_err(|err| err.expected())
288 }
289
290 fn with_str_property(self, key: &'static str, value: &'static str) -> Self {
291 self.map_err(|err| err.with_str_property(key, value))
292 }
293}