1use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
12#[serde(rename_all = "snake_case")]
13pub enum Kind {
14 InvalidInput,
16 Unauthorized,
18 Forbidden,
20 NotFound,
22 Conflict,
24 Canceled,
26 Unavailable,
28 Internal,
30}
31
32impl Kind {
33 pub fn as_str(&self) -> &'static str {
34 match self {
35 Kind::InvalidInput => "invalid_input",
36 Kind::Unauthorized => "unauthorized",
37 Kind::Forbidden => "forbidden",
38 Kind::NotFound => "not_found",
39 Kind::Conflict => "conflict",
40 Kind::Canceled => "canceled",
41 Kind::Unavailable => "unavailable",
42 Kind::Internal => "internal",
43 }
44 }
45}
46
47impl fmt::Display for Kind {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.write_str(self.as_str())
50 }
51}
52
53#[derive(Debug)]
61pub struct Error {
62 kind: Kind,
63 message: String,
64 cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
65 rich: Option<Box<Rich>>,
68}
69
70#[derive(Debug, Clone, Default)]
72pub struct Rich {
73 pub code: Option<String>,
75 pub detail: Option<serde_json::Map<String, serde_json::Value>>,
77 pub status: Option<u16>,
79}
80
81impl Error {
82 fn base(
83 kind: Kind,
84 message: String,
85 cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
86 ) -> Self {
87 Error {
88 kind,
89 message,
90 cause,
91 rich: None,
92 }
93 }
94
95 pub fn new(kind: Kind, msg: impl Into<String>) -> Self {
97 Error::base(kind, msg.into(), None)
98 }
99
100 pub fn errorf(kind: Kind, format: std::fmt::Arguments<'_>) -> Self {
102 Error::base(kind, fmt::format(format), None)
103 }
104
105 pub fn wrap(kind: Kind, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
107 Error::base(kind, String::new(), Some(Box::new(cause)))
108 }
109
110 pub fn wrap_msg(
112 kind: Kind,
113 cause: impl std::error::Error + Send + Sync + 'static,
114 msg: impl Into<String>,
115 ) -> Self {
116 Error::base(kind, msg.into(), Some(Box::new(cause)))
117 }
118
119 pub fn upgrade(cause: impl std::error::Error + Send + Sync + 'static) -> Self {
122 Error::base(Kind::Internal, String::new(), Some(Box::new(cause)))
123 }
124
125 fn rich_mut(&mut self) -> &mut Rich {
126 self.rich.get_or_insert_with(|| Box::new(Rich::default()))
127 }
128
129 pub fn with_code(mut self, code: impl Into<String>) -> Self {
131 self.rich_mut().code = Some(code.into());
132 self
133 }
134
135 pub fn with_detail(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
137 self.rich_mut()
138 .detail
139 .get_or_insert_with(serde_json::Map::new)
140 .insert(key.into(), value);
141 self
142 }
143
144 pub fn with_details(mut self, details: serde_json::Map<String, serde_json::Value>) -> Self {
146 self.rich_mut()
147 .detail
148 .get_or_insert_with(serde_json::Map::new)
149 .extend(details);
150 self
151 }
152
153 pub fn with_status(mut self, status: u16) -> Self {
155 self.rich_mut().status = Some(status);
156 self
157 }
158
159 pub fn inherit_rich(self, src: &(dyn std::error::Error + 'static)) -> Self {
162 self.inherit_values(rich_fields(src))
163 }
164
165 pub(crate) fn inherit_values(self, (code, detail, status): RichValues) -> Self {
167 if code.is_none() && detail.is_none() && status.is_none() {
168 return self;
169 }
170 let mut me = self;
171 let rich = me.rich_mut();
172 if rich.code.is_none() {
173 rich.code = code;
174 }
175 if rich.detail.is_none() {
176 rich.detail = detail;
177 }
178 if rich.status.is_none() {
179 rich.status = status;
180 }
181 me
182 }
183
184 pub fn kind(&self) -> Kind {
185 self.kind
186 }
187
188 pub fn status_override(&self) -> Option<u16> {
190 self.rich.as_ref().and_then(|r| r.status)
191 }
192
193 pub fn cause(&self) -> Option<&(dyn std::error::Error + Send + Sync)> {
195 self.cause.as_deref()
196 }
197}
198
199impl fmt::Display for Error {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 match (&self.message.is_empty(), &self.cause) {
202 (false, Some(cause)) => write!(f, "{}: {}", self.message, cause),
203 (false, None) => f.write_str(&self.message),
204 (true, Some(cause)) => write!(f, "{}", cause),
205 (true, None) => f.write_str(self.kind.as_str()),
206 }
207 }
208}
209
210impl std::error::Error for Error {
211 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
212 self.cause
213 .as_ref()
214 .map(|c| c.as_ref() as &(dyn std::error::Error + 'static))
215 }
216}
217
218impl From<std::io::Error> for Error {
219 fn from(e: std::io::Error) -> Self {
220 Error::wrap_msg(Kind::Internal, e, "io")
223 }
224}
225
226pub fn is_broken_pipe(err: &(dyn std::error::Error + 'static)) -> bool {
229 let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
230 while let Some(e) = cur {
231 if let Some(io) = e.downcast_ref::<std::io::Error>()
232 && io.kind() == std::io::ErrorKind::BrokenPipe
233 {
234 return true;
235 }
236 cur = e.source();
237 }
238 false
239}
240
241pub fn classify(err: &(dyn std::error::Error + 'static)) -> Option<Kind> {
245 let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
246 while let Some(e) = cur {
247 if let Some(ce) = e.downcast_ref::<Error>() {
248 return Some(ce.kind);
249 }
250 cur = e.source();
252 }
253 Some(Kind::Internal)
254}
255
256pub fn http_status(kind: Kind) -> u16 {
258 match kind {
259 Kind::InvalidInput => 400,
260 Kind::Unauthorized => 401,
261 Kind::Forbidden => 403,
262 Kind::NotFound => 404,
263 Kind::Conflict => 409,
264 Kind::Unavailable => 503,
265 Kind::Canceled => 499, Kind::Internal => 500,
267 }
268}
269
270pub fn exit_code(kind: Kind) -> i32 {
272 if kind == Kind::InvalidInput {
273 2 } else {
275 1
276 }
277}
278
279pub fn jsonrpc_code(kind: Kind) -> i64 {
282 match kind {
283 Kind::InvalidInput => -32602, Kind::NotFound => -32001,
285 Kind::Conflict => -32009,
286 Kind::Unauthorized => -32010,
287 Kind::Forbidden => -32011,
288 Kind::Canceled => -32012,
289 _ => -32603, }
291}
292
293pub type Result<T> = std::result::Result<T, Error>;
295
296pub fn from_chain<'a>(err: &'a (dyn std::error::Error + 'static)) -> Option<&'a Error> {
298 let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
299 while let Some(e) = cur {
300 if let Some(coded) = e.downcast_ref::<Error>() {
301 return Some(coded);
302 }
303 cur = e.source();
304 }
305 None
306}
307
308pub(crate) type RichValues = (
310 Option<String>,
311 Option<serde_json::Map<String, serde_json::Value>>,
312 Option<u16>,
313);
314
315pub(crate) fn rich_fields(err: &(dyn std::error::Error + 'static)) -> RichValues {
316 let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
317 let (mut code, mut detail, mut status) = (None, None, None);
318 while let Some(e) = cur {
319 if let Some(ce) = e.downcast_ref::<Error>()
320 && let Some(r) = &ce.rich
321 {
322 if code.is_none() {
323 code = r.code.clone();
324 }
325 if detail.is_none() {
326 detail = r.detail.clone();
327 }
328 if status.is_none() {
329 status = r.status;
330 }
331 }
332 cur = e.source();
333 }
334 (code, detail, status)
335}
336
337fn deepest_message(err: &(dyn std::error::Error + 'static)) -> String {
339 let mut cur: &(dyn std::error::Error + 'static) = err;
340 while let Some(src) = cur.source() {
341 cur = src;
342 }
343 cur.to_string()
344}
345
346#[derive(Debug, Clone, serde::Serialize)]
349pub struct Body {
350 pub error: String,
351 pub kind: Kind,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub code: Option<String>,
354 #[serde(skip_serializing_if = "Option::is_none")]
355 pub detail: Option<serde_json::Map<String, serde_json::Value>>,
356}
357
358impl Default for Body {
359 fn default() -> Self {
360 Body {
361 error: String::new(),
362 kind: Kind::Internal,
363 code: None,
364 detail: None,
365 }
366 }
367}
368
369pub fn error_body(err: &(dyn std::error::Error + 'static)) -> Body {
372 let mut b = Body {
373 error: deepest_message(err),
374 kind: classify(err).unwrap_or(Kind::Internal),
375 code: None,
376 detail: None,
377 };
378 let (code, detail, _) = rich_fields(err);
379 b.code = code;
380 b.detail = detail;
381 b
382}
383
384pub fn error_status(err: &(dyn std::error::Error + 'static)) -> u16 {
386 let (_, _, status) = rich_fields(err);
387 if let Some(s) = status {
388 return s;
389 }
390 http_status(classify(err).unwrap_or(Kind::Internal))
391}
392
393pub fn new(kind: Kind, msg: impl Into<String>) -> Error {
395 Error::new(kind, msg)
396}
397
398#[macro_export]
401macro_rules! errorf {
402 ($kind:expr, $($arg:tt)*) => {
403 $crate::errors::Error::errorf($kind, format_args!($($arg)*))
404 };
405}
406
407pub use errorf;
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 #[test]
413 fn mappings() {
414 assert_eq!(http_status(Kind::InvalidInput), 400);
415 assert_eq!(http_status(Kind::Unauthorized), 401);
416 assert_eq!(http_status(Kind::Forbidden), 403);
417 assert_eq!(http_status(Kind::NotFound), 404);
418 assert_eq!(http_status(Kind::Conflict), 409);
419 assert_eq!(http_status(Kind::Unavailable), 503);
420 assert_eq!(http_status(Kind::Canceled), 499);
421 assert_eq!(http_status(Kind::Internal), 500);
422
423 assert_eq!(exit_code(Kind::InvalidInput), 2);
424 assert_eq!(exit_code(Kind::NotFound), 1);
425
426 assert_eq!(jsonrpc_code(Kind::InvalidInput), -32602);
427 assert_eq!(jsonrpc_code(Kind::NotFound), -32001);
428 assert_eq!(jsonrpc_code(Kind::Conflict), -32009);
429 assert_eq!(jsonrpc_code(Kind::Unauthorized), -32010);
430 assert_eq!(jsonrpc_code(Kind::Forbidden), -32011);
431 assert_eq!(jsonrpc_code(Kind::Canceled), -32012);
432 assert_eq!(jsonrpc_code(Kind::Internal), -32603);
433 }
434
435 #[test]
436 fn classify_chain() {
437 let e = Error::wrap_msg(Kind::NotFound, Error::new(Kind::Conflict, "inner"), "outer");
438 assert_eq!(classify(&e), Some(Kind::NotFound));
439 let cause = std::error::Error::source(&e).unwrap();
442 assert_eq!(classify(cause), Some(Kind::Conflict));
443 }
444
445 #[test]
446 fn error_display_forms() {
447 assert_eq!(Error::new(Kind::NotFound, "x").to_string(), "x");
448 let wrapped = Error::wrap(Kind::InvalidInput, std::io::Error::other("ioerr"));
449 assert_eq!(wrapped.to_string(), "ioerr");
450 let both = Error::wrap_msg(Kind::Internal, std::io::Error::other("ioerr"), "boom");
451 assert_eq!(both.to_string(), "boom: ioerr");
452 }
453}