mithril_aggregator/runtime/
error.rs

1use slog::{crit, error, Logger};
2use thiserror::Error;
3
4use mithril_common::StdError;
5
6/// Error encountered or produced by the Runtime.
7/// This enum represents the faith of the errors produced during the state
8/// transitions.
9#[derive(Error, Debug)]
10pub enum RuntimeError {
11    /// Errors that need the runtime to try again without changing its state.
12    #[error("An error occurred, runtime state kept. message = '{message}'")]
13    KeepState {
14        /// error message
15        message: String,
16
17        /// Eventual caught error
18        #[source]
19        nested_error: Option<StdError>,
20    },
21    /// A Critical error means the Runtime stops and the software exits with an
22    /// error code.
23    #[error("A critical error occurred, aborting runtime. message = '{message}'")]
24    Critical {
25        /// error message
26        message: String,
27
28        /// Eventual caught error
29        #[source]
30        nested_error: Option<StdError>,
31    },
32    /// An error that needs to re-initialize the state machine.
33    #[error("An error occurred, runtime will be re-initialized. message = '{message}'")]
34    ReInit {
35        /// error message
36        message: String,
37
38        /// Eventual caught error
39        #[source]
40        nested_error: Option<StdError>,
41    },
42}
43
44impl RuntimeError {
45    /// Easy matching Critical errors.
46    pub fn is_critical(&self) -> bool {
47        matches!(self, RuntimeError::Critical { .. })
48    }
49
50    /// Create a new KeepState error
51    pub fn keep_state(message: &str, error: Option<StdError>) -> Self {
52        Self::KeepState {
53            message: message.to_string(),
54            nested_error: error,
55        }
56    }
57
58    /// Create a new Critical error
59    pub fn critical(message: &str, error: Option<StdError>) -> Self {
60        Self::Critical {
61            message: message.to_string(),
62            nested_error: error,
63        }
64    }
65
66    /// Write the error to the given logger.
67    pub fn write_to_log(&self, logger: &Logger) {
68        match self {
69            Self::KeepState { nested_error, .. } | Self::ReInit { nested_error, .. } => {
70                match nested_error {
71                    None => error!(logger, "{self}"),
72                    Some(err) => error!(logger, "{self}"; "nested_error" => ?err),
73                }
74            }
75            Self::Critical { nested_error, .. } => match nested_error {
76                None => crit!(logger, "{self}"),
77                Some(err) => crit!(logger, "{self}"; "nested_error" => ?err),
78            },
79        }
80    }
81}
82
83impl From<StdError> for RuntimeError {
84    fn from(value: StdError) -> Self {
85        Self::KeepState {
86            message: "Error caught, state preserved, will retry to cycle.".to_string(),
87            nested_error: Some(value),
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use anyhow::anyhow;
95
96    use crate::test_tools::TestLogger;
97
98    use super::*;
99
100    fn nested_error_debug_string(error: &RuntimeError) -> String {
101        let error = match error {
102            RuntimeError::KeepState { nested_error, .. } => nested_error,
103            RuntimeError::Critical { nested_error, .. } => nested_error,
104            RuntimeError::ReInit { nested_error, .. } => nested_error,
105        };
106        match error {
107            None => String::new(),
108            Some(err) => {
109                format!("{err:?}")
110            }
111        }
112    }
113
114    #[test]
115    fn log_critical_without_nested_error() {
116        let (logger, log_inspector) = TestLogger::memory();
117
118        let error = RuntimeError::Critical {
119            message: "Critical error".to_string(),
120            nested_error: None,
121        };
122        error.write_to_log(&logger);
123
124        assert!(log_inspector.contains_log(&format!("{error}")));
125        assert!(!log_inspector.contains_log("nested_error"));
126    }
127
128    #[test]
129    fn log_critical_with_nested_error() {
130        let (logger, log_inspector) = TestLogger::memory();
131
132        let error = RuntimeError::Critical {
133            message: "Critical error".to_string(),
134            nested_error: Some(
135                anyhow!("Another context error")
136                    .context("Context error")
137                    .context("Critical nested error"),
138            ),
139        };
140        error.write_to_log(&logger);
141
142        assert!(log_inspector.contains_log(&format!("{error}")));
143        assert!(log_inspector.contains_log(&nested_error_debug_string(&error)));
144    }
145
146    #[test]
147    fn log_keep_state_without_nested_error() {
148        let (logger, log_inspector) = TestLogger::memory();
149
150        let error = RuntimeError::KeepState {
151            message: "KeepState error".to_string(),
152            nested_error: None,
153        };
154        error.write_to_log(&logger);
155
156        assert!(log_inspector.contains_log(&format!("{error}")));
157        assert!(!log_inspector.contains_log("nested_error"));
158    }
159
160    #[test]
161    fn log_keep_state_with_nested_error() {
162        let (logger, log_inspector) = TestLogger::memory();
163
164        let error = RuntimeError::KeepState {
165            message: "KeepState error".to_string(),
166            nested_error: Some(
167                anyhow!("Another context error")
168                    .context("Context error")
169                    .context("KeepState nested error"),
170            ),
171        };
172        error.write_to_log(&logger);
173
174        assert!(log_inspector.contains_log(&format!("{error}")));
175        assert!(log_inspector.contains_log(&nested_error_debug_string(&error)));
176    }
177
178    #[test]
179    fn log_reinit_without_nested_error() {
180        let (logger, log_inspector) = TestLogger::memory();
181
182        let error = RuntimeError::ReInit {
183            message: "ReInit error".to_string(),
184            nested_error: None,
185        };
186        error.write_to_log(&logger);
187
188        assert!(log_inspector.contains_log(&format!("{error}")));
189        assert!(!log_inspector.contains_log("nested_error"));
190    }
191
192    #[test]
193    fn log_reinit_with_nested_error() {
194        let (logger, log_inspector) = TestLogger::memory();
195
196        let error = RuntimeError::ReInit {
197            message: "ReInit error".to_string(),
198            nested_error: Some(
199                anyhow!("Another context error")
200                    .context("Context error")
201                    .context("ReInit nested error"),
202            ),
203        };
204        error.write_to_log(&logger);
205
206        assert!(log_inspector.contains_log(&format!("{error}")));
207        assert!(log_inspector.contains_log(&nested_error_debug_string(&error)));
208    }
209}