/ sinks / sink-common / src / error.rs
error.rs
  1  use std::{fmt, process::ExitCode};
  2  
  3  use error_stack::{report, Context, Report, Result, ResultExt};
  4  
  5  /// Sink error.
  6  ///
  7  /// Sink developers should default to returning `SinkError::Temporary` for all errors.
  8  /// `SinkError::Configuration` should be returned for configuration-related errors.
  9  /// `SinkError::Fatal` should only be returned from `Sink::handle_data` and `Sink::handle_invalidate`.
 10  #[derive(Debug)]
 11  pub enum SinkError {
 12      /// Configuration error. Should not retry.
 13      Configuration,
 14      /// Temporary error. Should retry.
 15      Temporary,
 16      /// Fatal error. Should not retry.
 17      Fatal,
 18      // Status server error
 19      Status,
 20      // Persistence error
 21      Persistence,
 22      // Load script error
 23      LoadScript,
 24  }
 25  
 26  pub trait ReportExt {
 27      fn to_exit_code(&self) -> ExitCode;
 28  }
 29  
 30  impl error_stack::Context for SinkError {}
 31  
 32  impl fmt::Display for SinkError {
 33      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 34          match self {
 35              SinkError::Configuration => f.write_str("sink configuration error"),
 36              SinkError::Temporary => f.write_str("temporary sink error"),
 37              SinkError::Fatal => f.write_str("fatal sink error"),
 38              SinkError::Persistence => f.write_str("persistence client operation failed"),
 39              SinkError::Status => f.write_str("status server operation failed"),
 40              SinkError::LoadScript => f.write_str("load script failed"),
 41          }
 42      }
 43  }
 44  
 45  impl<T> ReportExt for Result<T, SinkError> {
 46      fn to_exit_code(&self) -> ExitCode {
 47          match self {
 48              Ok(_) => ExitCode::SUCCESS,
 49              Err(err) => {
 50                  eprintln!("{:?}", err);
 51                  // Exit codes based on sysexits.h
 52                  match err.downcast_ref::<SinkError>() {
 53                      Some(SinkError::Configuration) => ExitCode::from(78),
 54                      Some(SinkError::Temporary) => ExitCode::from(75),
 55                      Some(SinkError::Fatal) => ExitCode::FAILURE,
 56                      Some(_) => ExitCode::FAILURE,
 57                      None => ExitCode::FAILURE,
 58                  }
 59              }
 60          }
 61      }
 62  }
 63  
 64  impl SinkError {
 65      pub fn configuration(reason: &str) -> Report<SinkError> {
 66          report!(SinkError::Configuration).attach_printable(reason.to_string())
 67      }
 68      pub fn temporary(reason: &str) -> Report<SinkError> {
 69          report!(SinkError::Temporary).attach_printable(reason.to_string())
 70      }
 71      pub fn fatal(reason: &str) -> Report<SinkError> {
 72          report!(SinkError::Fatal).attach_printable(reason.to_string())
 73      }
 74      pub fn status(reason: &str) -> Report<SinkError> {
 75          report!(SinkError::Status).attach_printable(reason.to_string())
 76      }
 77      pub fn load_script(reason: &str) -> Report<SinkError> {
 78          report!(SinkError::LoadScript).attach_printable(reason.to_string())
 79      }
 80  }
 81  
 82  pub trait SinkErrorResultExt {
 83      type Ok;
 84      fn configuration(self, reason: &str) -> Result<Self::Ok, SinkError>;
 85      fn temporary(self, reason: &str) -> Result<Self::Ok, SinkError>;
 86      fn fatal(self, reason: &str) -> Result<Self::Ok, SinkError>;
 87      fn status(self, reason: &str) -> Result<Self::Ok, SinkError>;
 88      fn persistence(self, reason: &str) -> Result<Self::Ok, SinkError>;
 89      fn load_script(self, reason: &str) -> Result<Self::Ok, SinkError>;
 90  }
 91  
 92  impl<T, C> SinkErrorResultExt for core::result::Result<T, C>
 93  where
 94      C: Context,
 95  {
 96      type Ok = T;
 97  
 98      fn configuration(self, reason: &str) -> Result<T, SinkError> {
 99          self.change_context(SinkError::Configuration)
100              .attach_printable(reason.to_string())
101      }
102  
103      fn temporary(self, reason: &str) -> Result<T, SinkError> {
104          self.change_context(SinkError::Temporary)
105              .attach_printable(reason.to_string())
106      }
107  
108      fn fatal(self, reason: &str) -> Result<T, SinkError> {
109          self.change_context(SinkError::Fatal)
110              .attach_printable(reason.to_string())
111      }
112  
113      fn status(self, reason: &str) -> Result<T, SinkError> {
114          self.change_context(SinkError::Status)
115              .attach_printable(reason.to_string())
116      }
117  
118      fn persistence(self, reason: &str) -> Result<T, SinkError> {
119          self.change_context(SinkError::Persistence)
120              .attach_printable(reason.to_string())
121      }
122  
123      fn load_script(self, reason: &str) -> Result<T, SinkError> {
124          self.change_context(SinkError::LoadScript)
125              .attach_printable(reason.to_string())
126      }
127  }
128  
129  pub trait SinkErrorReportExt {
130      type Ok;
131      fn configuration(self, reason: &str) -> Report<SinkError>;
132      fn temporary(self, reason: &str) -> Report<SinkError>;
133      fn fatal(self, reason: &str) -> Report<SinkError>;
134      fn status(self, reason: &str) -> Report<SinkError>;
135      fn load_script(self, reason: &str) -> Report<SinkError>;
136  }
137  
138  impl<C> SinkErrorReportExt for Report<C> {
139      type Ok = ();
140  
141      fn configuration(self, reason: &str) -> Report<SinkError> {
142          self.change_context(SinkError::Configuration)
143              .attach_printable(reason.to_string())
144      }
145  
146      fn temporary(self, reason: &str) -> Report<SinkError> {
147          self.change_context(SinkError::Temporary)
148              .attach_printable(reason.to_string())
149      }
150  
151      fn fatal(self, reason: &str) -> Report<SinkError> {
152          self.change_context(SinkError::Fatal)
153              .attach_printable(reason.to_string())
154      }
155  
156      fn status(self, reason: &str) -> Report<SinkError> {
157          self.change_context(SinkError::Status)
158              .attach_printable(reason.to_string())
159      }
160  
161      fn load_script(self, reason: &str) -> Report<SinkError> {
162          self.change_context(SinkError::LoadScript)
163              .attach_printable(reason.to_string())
164      }
165  }