1use std::{any::Any, error::Error as StdError, sync::Arc};
16
17use http::StatusCode as HttpStatusCode;
18use serde::Serialize;
19use snafu::Snafu;
20use strum::EnumProperty;
21use tonic::Code as TonicCode;
22
23#[derive(
24 Clone,
25 Copy,
26 Debug,
27 Eq,
28 PartialEq,
29 Serialize,
30 strum_macros::EnumProperty,
31 strum_macros::EnumString,
32)]
33#[serde(rename_all = "snake_case")]
34#[strum(serialize_all = "snake_case")]
35pub enum StatusCode {
36 #[strum(props(http_status = "400", tonic_code = "3"))]
37 InvalidArgument,
38 #[strum(props(http_status = "404", tonic_code = "5"))]
39 NotFound,
40 #[strum(props(http_status = "401", tonic_code = "16"))]
41 Unauthorized,
42 #[strum(props(http_status = "403", tonic_code = "7"))]
43 Forbidden,
44 #[strum(props(http_status = "409", tonic_code = "6"))]
45 Conflict,
46 #[strum(props(http_status = "500", tonic_code = "13"))]
47 Internal,
48 #[strum(props(http_status = "500", tonic_code = "13"))]
49 Unknown,
50}
51
52impl StatusCode {
53 #[must_use]
54 pub fn http_status(self) -> HttpStatusCode {
55 self.get_str("http_status")
56 .and_then(|value| value.parse::<u16>().ok())
57 .and_then(|value| HttpStatusCode::from_u16(value).ok())
58 .unwrap_or(HttpStatusCode::INTERNAL_SERVER_ERROR)
59 }
60
61 #[must_use]
62 pub fn tonic_code(self) -> TonicCode {
63 let value = self
64 .get_str("tonic_code")
65 .and_then(|value| value.parse::<i32>().ok())
66 .unwrap_or(TonicCode::Internal as i32);
67 TonicCode::from_i32(value)
68 }
69}
70
71pub trait StackError: StdError {
72 fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>);
73
74 fn next(&self) -> Option<&dyn StackError>;
75
76 fn last(&self) -> &dyn StackError
77 where
78 Self: Sized,
79 {
80 let Some(mut result) = self.next() else {
81 return self;
82 };
83 while let Some(err) = result.next() {
84 result = err;
85 }
86 result
87 }
88
89 fn transparent(&self) -> bool { false }
90}
91
92pub trait ErrorExt: StackError {
93 fn status_code(&self) -> StatusCode { StatusCode::Unknown }
94
95 fn as_any(&self) -> &dyn Any;
96
97 fn output_msg(&self) -> String
98 where
99 Self: Sized,
100 {
101 match self.status_code() {
102 StatusCode::Unknown | StatusCode::Internal => {
103 format!("Internal error: {}", self.status_code() as u32)
104 }
105 _ => {
106 let error = self.last();
107 error.source().map_or_else(
108 || format!("{error}"),
109 |external_error| {
110 let mut root = external_error;
111 while let Some(source) = root.source() {
112 root = source;
113 }
114 if error.transparent() {
115 format!("{root}")
116 } else {
117 format!("{error}: {root}")
118 }
119 },
120 )
121 }
122 }
123 }
124
125 fn root_cause(&self) -> Option<&dyn StdError>
126 where
127 Self: Sized,
128 {
129 let error = self.last();
130 let mut source = error.source()?;
131 while let Some(next) = source.source() {
132 source = next;
133 }
134 Some(source)
135 }
136}
137
138impl<T: ?Sized + StackError> StackError for Arc<T> {
139 fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
140 self.as_ref().debug_fmt(layer, buf);
141 }
142
143 fn next(&self) -> Option<&dyn StackError> { self.as_ref().next() }
144}
145
146impl<T: StackError> StackError for Box<T> {
147 fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
148 self.as_ref().debug_fmt(layer, buf);
149 }
150
151 fn next(&self) -> Option<&dyn StackError> { self.as_ref().next() }
152}
153
154pub type Result<T> = std::result::Result<T, Error>;
155
156#[derive(Snafu, Debug)]
157#[snafu(visibility(pub))]
158pub enum Error {
159 #[snafu(transparent)]
160 Network {
161 source: NetworkError,
162 #[snafu(implicit)]
163 loc: snafu::Location,
164 },
165}
166
167#[derive(Snafu, Debug)]
168#[snafu(visibility(pub))]
169pub enum NetworkError {
170 #[snafu(display("Failed to connect to {addr}"))]
171 ConnectionError {
172 addr: String,
173 #[snafu(source)]
174 source: std::io::Error,
175 #[snafu(implicit)]
176 loc: snafu::Location,
177 },
178
179 #[snafu(display("Failed to parse address {addr}"))]
180 ParseAddressError {
181 addr: String,
182 #[snafu(source)]
183 source: std::net::AddrParseError,
184 #[snafu(implicit)]
185 loc: snafu::Location,
186 },
187}