1use std::{error::Error as StdError, fmt, result::Result as StdResult};
2
3#[cfg(test)]
4mod test;
5
6pub mod prelude {
9 pub use super::{Error, ErrorKind, Result, SiftError};
10}
11
12pub type Result<T> = StdResult<T, Error>;
14pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;
15
16pub trait SiftError<T, C>
18where
19 C: fmt::Display + Send + Sync + 'static,
20{
21 fn context(self, ctx: C) -> Result<T>;
23
24 fn with_context<F>(self, op: F) -> Result<T>
26 where
27 F: Fn() -> C;
28
29 fn help(self, txt: C) -> Result<T>;
31}
32
33#[derive(Debug)]
35pub struct Error {
36 context: Option<Vec<String>>,
37 help: Option<String>,
38 kind: ErrorKind,
39 inner: Option<BoxedError>,
40}
41
42impl StdError for Error {}
43
44impl Error {
45 pub fn new<E>(kind: ErrorKind, err: E) -> Self
47 where
48 E: StdError + Send + Sync + 'static,
49 {
50 let inner = Box::new(err);
51 Self {
52 inner: Some(inner),
53 kind,
54 context: None,
55 help: None,
56 }
57 }
58
59 pub fn new_msg<S: AsRef<str>>(kind: ErrorKind, msg: S) -> Self {
61 Self {
62 inner: None,
63 kind,
64 context: Some(vec![msg.as_ref().to_string()]),
65 help: None,
66 }
67 }
68
69 pub fn new_general<S: AsRef<str>>(msg: S) -> Self {
72 Self::new_msg(ErrorKind::GeneralError, msg)
73 }
74
75 pub fn new_arg_error<S: AsRef<str>>(msg: S) -> Self {
77 Self::new_msg(ErrorKind::ArgumentValidationError, msg)
78 }
79
80 pub fn new_empty_response<S: AsRef<str>>(msg: S) -> Self {
84 Self {
85 inner: None,
86 kind: ErrorKind::EmptyResponseError,
87 context: Some(vec![msg.as_ref().to_string()]),
88 help: Some("please contact Sift".to_string()),
89 }
90 }
91
92 pub fn kind(&self) -> ErrorKind {
94 self.kind
95 }
96}
97
98#[derive(Debug, PartialEq, Copy, Clone)]
100pub enum ErrorKind {
101 ArgumentValidationError,
103 ConfigError,
105 GrpcConnectError,
107 RetrieveRunError,
109 RetrieveAssetError,
111 UpdateRunError,
113 RetrieveIngestionConfigError,
115 CreateRunError,
117 CreateIngestionConfigError,
119 CreateFlowError,
121 NotFoundError,
123 IoError,
125 NumberConversionError,
127 TimeConversionError,
129 StreamError,
131 RetriesExhausted,
133 BackupsError,
135 IncompatibleIngestionConfigChange,
138 UnknownFlow,
141 EmptyResponseError,
143 ProtobufDecodeError,
145 BackupIntegrityError,
147 BackupLimitReached,
149 GeneralError,
151}
152
153impl<T, C> SiftError<T, C> for Result<T>
154where
155 C: fmt::Display + Send + Sync + 'static,
156{
157 fn with_context<F>(self, op: F) -> Result<T>
158 where
159 F: Fn() -> C,
160 {
161 self.map_err(|mut err| {
162 if let Some(context) = err.context.as_mut() {
163 context.push(format!("{}", op()));
164 } else {
165 err.context = Some(vec![format!("{}", op())]);
166 }
167 err
168 })
169 }
170
171 fn context(self, ctx: C) -> Self {
172 self.map_err(|mut err| {
173 if let Some(context) = err.context.as_mut() {
174 context.push(format!("{ctx}"));
175 } else {
176 err.context = Some(vec![format!("{ctx}")]);
177 }
178 err
179 })
180 }
181
182 fn help(self, txt: C) -> Self {
183 self.map_err(|mut err| {
184 err.help = Some(format!("{txt}"));
185 err
186 })
187 }
188}
189
190impl fmt::Display for ErrorKind {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 match self {
193 Self::GrpcConnectError => write!(f, "GrpcConnectError"),
194 Self::RetriesExhausted => write!(f, "RetriesExhausted"),
195 Self::RetrieveAssetError => write!(f, "RetrieveAssetError"),
196 Self::RetrieveRunError => write!(f, "RetrieveRunError"),
197 Self::RetrieveIngestionConfigError => write!(f, "RetrieveIngestionConfigError"),
198 Self::EmptyResponseError => write!(f, "EmptyResponseError"),
199 Self::NotFoundError => write!(f, "NotFoundError"),
200 Self::CreateRunError => write!(f, "CreateRunError"),
201 Self::ArgumentValidationError => write!(f, "ArgumentValidationError"),
202 Self::GeneralError => write!(f, "GeneralError"),
203 Self::IoError => write!(f, "IoError"),
204 Self::ConfigError => write!(f, "ConfigError"),
205 Self::UpdateRunError => write!(f, "UpdateRunError"),
206 Self::CreateIngestionConfigError => write!(f, "CreateIngestionConfigError"),
207 Self::NumberConversionError => write!(f, "NumberConversionError"),
208 Self::CreateFlowError => write!(f, "CreateFlowError"),
209 Self::TimeConversionError => write!(f, "TimeConversionError"),
210 Self::StreamError => write!(f, "StreamError"),
211 Self::UnknownFlow => write!(f, "UnknownFlow"),
212 Self::BackupsError => write!(f, "BackupsError"),
213 Self::BackupIntegrityError => write!(f, "BackupIntegrityError"),
214 Self::BackupLimitReached => write!(f, "BackupLimitReached"),
215 Self::ProtobufDecodeError => write!(f, "ProtobufDecodeError"),
216 Self::IncompatibleIngestionConfigChange => {
217 write!(f, "IncompatibleIngestionConfigChange")
218 }
219 }
220 }
221}
222
223const NEW_LINE_DELIMITER: &str = "\n ";
224
225impl fmt::Display for Error {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 let Error {
228 context,
229 kind,
230 help,
231 inner,
232 } = self;
233
234 let root_cause = inner.as_ref().map(|e| format!("{e}"));
235
236 let (most_recent_cause, chain) = context.as_ref().map_or_else(
237 || {
238 let root = root_cause.clone().unwrap_or_default();
239 (String::new(), format!("- {root}"))
240 },
241 |c| {
242 let mut cause_iter = c.iter().rev();
243
244 if let Some(first) = cause_iter.next() {
245 let mut cause_chain = cause_iter
246 .map(|s| format!("- {s}"))
247 .collect::<Vec<String>>()
248 .join(NEW_LINE_DELIMITER);
249
250 if let Some(root) = root_cause.clone() {
251 if cause_chain.is_empty() {
252 cause_chain = format!("- {root}");
253 } else {
254 cause_chain = format!("{cause_chain}{NEW_LINE_DELIMITER}- {root}");
255 }
256 }
257
258 (first.clone(), cause_chain)
259 } else {
260 (
261 String::new(),
262 root_cause
263 .as_ref()
264 .map_or_else(String::new, |s| format!("- {s}")),
265 )
266 }
267 },
268 );
269
270 match help {
271 Some(help_txt) if most_recent_cause.is_empty() => {
272 writeln!(
273 f,
274 "[{kind}]\n\n[cause]:{NEW_LINE_DELIMITER}{chain}\n\n[help]:{NEW_LINE_DELIMITER}- {help_txt}"
275 )
276 }
277 None if most_recent_cause.is_empty() => {
278 writeln!(f, "[{kind}]\n\n[cause]:{NEW_LINE_DELIMITER}{chain}")
279 }
280 Some(help_txt) => {
281 writeln!(
282 f,
283 "[{kind}]: {most_recent_cause}\n\n[cause]:{NEW_LINE_DELIMITER}{chain}\n\n[help]:{NEW_LINE_DELIMITER}- {help_txt}"
284 )
285 }
286 None => {
287 writeln!(
288 f,
289 "[{kind}]: {most_recent_cause}\n\n[cause]:{NEW_LINE_DELIMITER}{chain}"
290 )
291 }
292 }
293 }
294}
295
296impl From<std::io::Error> for Error {
297 fn from(value: std::io::Error) -> Self {
298 Self {
299 context: None,
300 help: None,
301 inner: Some(Box::new(value)),
302 kind: ErrorKind::IoError,
303 }
304 }
305}