mithril_aggregator/runtime/
error.rs1use slog::{crit, error, Logger};
2use thiserror::Error;
3
4use mithril_common::StdError;
5
6#[derive(Error, Debug)]
10pub enum RuntimeError {
11 #[error("An error occurred, runtime state kept. message = '{message}'")]
13 KeepState {
14 message: String,
16
17 #[source]
19 nested_error: Option<StdError>,
20 },
21 #[error("A critical error occurred, aborting runtime. message = '{message}'")]
24 Critical {
25 message: String,
27
28 #[source]
30 nested_error: Option<StdError>,
31 },
32 #[error("An error occurred, runtime will be re-initialized. message = '{message}'")]
34 ReInit {
35 message: String,
37
38 #[source]
40 nested_error: Option<StdError>,
41 },
42}
43
44impl RuntimeError {
45 pub fn is_critical(&self) -> bool {
47 matches!(self, RuntimeError::Critical { .. })
48 }
49
50 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 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 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}