mithril_signer/runtime/
error.rs

1use slog::{crit, error, Logger};
2use thiserror::Error;
3
4use mithril_common::entities::EpochError;
5use mithril_common::StdError;
6
7use crate::RunnerError;
8
9/// RuntimeError
10/// Error kinds tied to their faith in the state machine.
11#[derive(Error, Debug)]
12pub enum RuntimeError {
13    /// KeepState error means the runtime will keep its state and try to cycle
14    /// again.
15    #[error("An error occurred, runtime state kept. message = '{message}'")]
16    KeepState {
17        /// Context error message
18        message: String,
19
20        /// Eventual previous error message
21        #[source]
22        nested_error: Option<StdError>,
23    },
24    /// Critical error means the runtime will exit and the software will return
25    /// an error code.
26    #[error("A critical error occurred, aborting runtime. message = '{message}'")]
27    Critical {
28        /// Context error message
29        message: String,
30
31        /// Eventual previous error message
32        #[source]
33        nested_error: Option<StdError>,
34    },
35}
36
37impl RuntimeError {
38    /// Easy matching Critical errors.
39    pub fn is_critical(&self) -> bool {
40        matches!(
41            self,
42            RuntimeError::Critical {
43                message: _,
44                nested_error: _
45            }
46        )
47    }
48
49    /// Write the error to the given logger.
50    pub fn write_to_log(&self, logger: &Logger) {
51        match self {
52            Self::KeepState { nested_error, .. } => match nested_error {
53                None => error!(logger, "{self}"),
54                Some(err) => error!(logger, "{self}"; "nested_error" => ?err),
55            },
56            Self::Critical { nested_error, .. } => match nested_error {
57                None => crit!(logger, "{self}"),
58                Some(err) => crit!(logger, "{self}"; "nested_error" => ?err),
59            },
60        }
61    }
62}
63
64impl From<RunnerError> for RuntimeError {
65    fn from(value: RunnerError) -> Self {
66        Self::KeepState {
67            message: "runner failed".to_string(),
68            nested_error: Some(value.into()),
69        }
70    }
71}
72
73impl From<EpochError> for RuntimeError {
74    fn from(value: EpochError) -> Self {
75        Self::KeepState {
76            message: "Epoch offset conversion failed".to_string(),
77            nested_error: Some(value.into()),
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use anyhow::anyhow;
85
86    use crate::test_tools::TestLogger;
87
88    use super::*;
89
90    fn nested_error_debug_string(error: &RuntimeError) -> String {
91        let error = match error {
92            RuntimeError::KeepState { nested_error, .. } => nested_error,
93            RuntimeError::Critical { nested_error, .. } => nested_error,
94        };
95        match error {
96            None => String::new(),
97            Some(err) => {
98                format!("{err:?}")
99            }
100        }
101    }
102
103    #[test]
104    fn log_critical_without_nested_error() {
105        let (logger, log_inspector) = TestLogger::memory();
106
107        let error = RuntimeError::Critical {
108            message: "Critical error".to_string(),
109            nested_error: None,
110        };
111        error.write_to_log(&logger);
112
113        assert!(log_inspector.contains_log(&format!("{error}")));
114        assert!(!log_inspector.contains_log("nested_error"));
115    }
116
117    #[test]
118    fn log_critical_with_nested_error() {
119        let (logger, log_inspector) = TestLogger::memory();
120
121        let error = RuntimeError::Critical {
122            message: "Critical error".to_string(),
123            nested_error: Some(
124                anyhow!("Another context error")
125                    .context("Context error")
126                    .context("Critical nested error"),
127            ),
128        };
129        error.write_to_log(&logger);
130
131        assert!(log_inspector.contains_log(&format!("{error}")));
132        assert!(log_inspector.contains_log(&nested_error_debug_string(&error)));
133    }
134
135    #[test]
136    fn log_keep_state_without_nested_error() {
137        let (logger, log_inspector) = TestLogger::memory();
138
139        let error = RuntimeError::KeepState {
140            message: "KeepState error".to_string(),
141            nested_error: None,
142        };
143        error.write_to_log(&logger);
144
145        assert!(log_inspector.contains_log(&format!("{error}")));
146        assert!(!log_inspector.contains_log("nested_error"));
147    }
148
149    #[test]
150    fn log_keep_state_with_nested_error() {
151        let (logger, log_inspector) = TestLogger::memory();
152
153        let error = RuntimeError::KeepState {
154            message: "KeepState error".to_string(),
155            nested_error: Some(
156                anyhow!("Another context error")
157                    .context("Context error")
158                    .context("KeepState nested error"),
159            ),
160        };
161        error.write_to_log(&logger);
162
163        assert!(log_inspector.contains_log(&format!("{error}")));
164        assert!(log_inspector.contains_log(&nested_error_debug_string(&error)));
165    }
166}