reifydb_core/error/diagnostic/
internal.rs1use reifydb_runtime::clock::Clock;
5use reifydb_type::{error::diagnostic::Diagnostic, fragment::Fragment};
6
7pub fn internal_with_context(
10 reason: impl Into<String>,
11 file: &str,
12 line: u32,
13 column: u32,
14 function: &str,
15 module_path: &str,
16) -> Diagnostic {
17 let reason = reason.into();
18
19 let error_id = format!(
21 "ERR-{}-{}:{}",
22 Clock::default().now_millis(),
23 file.rsplit('/').next().unwrap_or(file).replace(".rs", ""),
24 line
25 );
26
27 let detailed_message = format!("Internal error [{}]: {}", error_id, reason);
28
29 let location_info =
30 format!("Location: {}:{}:{}\nFunction: {}\nModule: {}", file, line, column, function, module_path);
31
32 let help_message = format!(
33 "This is an internal error that should never occur in normal operation.\n\n\
34 Please file a bug report at: https://github.com/reifydb/reifydb/issues\n\n\
35 Include the following information:\n\
36 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\
37 Error ID: {}\n\
38 {}\n\
39 Version: {}\n\
40 Build: {} ({})\n\
41 Platform: {} {}\n\
42 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
43 error_id,
44 location_info,
45 env!("CARGO_PKG_VERSION"),
46 option_env!("GIT_HASH").unwrap_or("unknown"),
47 option_env!("BUILD_DATE").unwrap_or("unknown"),
48 std::env::consts::OS,
49 std::env::consts::ARCH
50 );
51
52 Diagnostic {
53 code: "INTERNAL_ERROR".to_string(),
54 statement: None,
55 message: detailed_message,
56 column: None,
57 fragment: Fragment::None,
58 label: Some(format!("Internal invariant violated at {}:{}:{}", file, line, column)),
59 help: Some(help_message),
60 notes: vec![
61 format!("Error occurred in function: {}", function),
62 "This error indicates a critical internal inconsistency.".to_string(),
63 "Your database may be in an inconsistent state.".to_string(),
64 "Consider creating a backup before continuing operations.".to_string(),
65 format!("Error tracking ID: {}", error_id),
66 ],
67 cause: None,
68 operator_chain: None,
69 }
70}
71
72pub fn internal(reason: impl Into<String>) -> Diagnostic {
74 internal_with_context(reason, "unknown", 0, 0, "unknown", "unknown")
75}
76
77pub fn shutdown(component: impl Into<String>) -> Diagnostic {
80 let component = component.into();
81
82 Diagnostic {
83 code: "SHUTDOWN".to_string(),
84 statement: None,
85 message: format!("{} is shutting down", component),
86 column: None,
87 fragment: Fragment::None,
88 label: Some(format!("{} is no longer accepting requests", component)),
89 help: Some(format!(
90 "This operation failed because {} is shutting down.\n\
91 This is expected during database shutdown.",
92 component
93 )),
94 notes: vec![
95 "This is not an error - the system is shutting down gracefully".to_string(),
96 "Operations submitted during shutdown will be rejected".to_string(),
97 ],
98 cause: None,
99 operator_chain: None,
100 }
101}
102
103#[macro_export]
105macro_rules! internal {
106 ($reason:expr) => {
107 $crate::error::diagnostic::internal::internal_with_context(
108 $reason,
109 file!(),
110 line!(),
111 column!(),
112 {
113 fn f() {}
114 fn type_name_of<T>(_: T) -> &'static str {
115 std::any::type_name::<T>()
116 }
117 let name = type_name_of(f);
118 &name[..name.len() - 3]
119 },
120 module_path!()
121 )
122 };
123 ($fmt:expr, $($arg:tt)*) => {
124 $crate::error::diagnostic::internal::internal_with_context(
125 format!($fmt, $($arg)*),
126 file!(),
127 line!(),
128 column!(),
129 {
130 fn f() {}
131 fn type_name_of<T>(_: T) -> &'static str {
132 std::any::type_name::<T>()
133 }
134 let name = type_name_of(f);
135 &name[..name.len() - 3]
136 },
137 module_path!()
138 )
139 };
140}
141
142#[macro_export]
145macro_rules! internal_error {
146 ($reason:expr) => {
147 reifydb_type::error::Error($crate::internal!($reason))
148 };
149 ($fmt:expr, $($arg:tt)*) => {
150 reifydb_type::error::Error($crate::internal!($fmt, $($arg)*))
151 };
152}
153
154#[macro_export]
157macro_rules! internal_err {
158 ($reason:expr) => {
159 Err($crate::internal_error!($reason))
160 };
161 ($fmt:expr, $($arg:tt)*) => {
162 Err($crate::internal_error!($fmt, $($arg)*))
163 };
164}
165
166#[macro_export]
169macro_rules! return_internal_error {
170 ($reason:expr) => {
171 return $crate::internal_err!($reason)
172 };
173 ($fmt:expr, $($arg:tt)*) => {
174 return $crate::internal_err!($fmt, $($arg)*)
175 };
176}
177
178#[cfg(test)]
179pub mod tests {
180 use std::{thread::sleep, time::Duration};
181
182 use super::*;
183
184 #[derive(Debug)]
185 #[allow(dead_code)]
186 struct TestStruct {
187 value: i32,
188 name: String,
189 }
190
191 #[test]
192 fn test_internal_error_literal_string() {
193 let diagnostic = internal!("simple error message");
194
195 assert_eq!(diagnostic.code, "INTERNAL_ERROR");
196 assert!(diagnostic.message.contains("simple error message"));
197 assert!(diagnostic.help.is_some());
198 assert!(diagnostic.help.as_ref().unwrap().contains("bug report"));
199 assert!(diagnostic.notes.len() > 0);
200 }
201
202 #[test]
203 fn test_internal_error_with_format() {
204 let value = 42;
205 let name = "test";
206 let diagnostic = internal!("Error with value: {} and name: {}", value, name);
207
208 assert_eq!(diagnostic.code, "INTERNAL_ERROR");
209 assert!(diagnostic.message.contains("Error with value: 42 and name: test"));
210 assert!(diagnostic.label.is_some());
211 assert!(diagnostic.label.as_ref().unwrap().contains("Internal invariant violated"));
212 }
213
214 #[test]
215 fn test_internal_error_inline_variable_syntax() {
216 let test_struct = TestStruct {
217 value: 42,
218 name: "test".to_string(),
219 };
220
221 let diagnostic = internal!("Test struct: {:?}", test_struct);
222
223 assert_eq!(diagnostic.code, "INTERNAL_ERROR");
224 assert!(diagnostic.message.contains("TestStruct"));
225 assert!(diagnostic.message.contains("value: 42"));
226 assert!(diagnostic.message.contains("name: \"test\""));
227 }
228
229 #[test]
230 fn test_internal_err_literal_string() {
231 let result: Result<(), reifydb_type::error::Error> = internal_err!("test error");
232
233 assert!(result.is_err());
234 let error = result.unwrap_err();
235 assert_eq!(error.0.code, "INTERNAL_ERROR");
236 assert!(error.0.message.contains("test error"));
237 }
238
239 #[test]
240 fn test_internal_err_with_format() {
241 let code = "ERR_123";
242 let line = 456;
243 let result: Result<(), reifydb_type::error::Error> =
244 internal_err!("Error code: {} at line {}", code, line);
245
246 assert!(result.is_err());
247 let error = result.unwrap_err();
248 assert_eq!(error.0.code, "INTERNAL_ERROR");
249 assert!(error.0.message.contains("Error code: ERR_123 at line 456"));
250 }
251
252 #[test]
253 fn test_internal_function() {
254 let diagnostic = internal("basic internal error");
255
256 assert_eq!(diagnostic.code, "INTERNAL_ERROR");
257 assert!(diagnostic.message.contains("basic internal error"));
258 assert!(diagnostic.label.is_some());
259 assert!(diagnostic.label.as_ref().unwrap().contains("unknown:0:0"));
260 }
261
262 #[test]
263 fn test_internal_with_context_function() {
264 let diagnostic =
265 internal_with_context("context error", "test.rs", 100, 20, "test_function", "test::module");
266
267 assert_eq!(diagnostic.code, "INTERNAL_ERROR");
268 assert!(diagnostic.message.contains("context error"));
269 assert!(diagnostic.label.is_some());
270 assert!(diagnostic.label.as_ref().unwrap().contains("test.rs:100:20"));
271 assert!(diagnostic.notes.iter().any(|n| n.contains("test_function")));
272 assert!(diagnostic.help.is_some());
273 let help = diagnostic.help.as_ref().unwrap();
274 assert!(help.contains("test.rs:100:20"));
275 assert!(help.contains("test::module"));
276 }
277
278 #[test]
279 fn test_return_internal_error_in_function() {
280 fn test_function_literal() -> Result<(), reifydb_type::error::Error> {
281 return_internal_error!("function error");
282 }
283
284 let result = test_function_literal();
285 assert!(result.is_err());
286 let error = result.unwrap_err();
287 assert_eq!(error.0.code, "INTERNAL_ERROR");
288 assert!(error.0.message.contains("function error"));
289 }
290
291 #[test]
292 fn test_return_internal_error_with_format() {
293 fn test_function_format(val: u32) -> Result<(), reifydb_type::error::Error> {
294 return_internal_error!("Invalid value: {:#04x}", val);
295 }
296
297 let result = test_function_format(255);
298 assert!(result.is_err());
299 let error = result.unwrap_err();
300 assert_eq!(error.0.code, "INTERNAL_ERROR");
301 assert!(error.0.message.contains("Invalid value: 0xff"));
302 }
303
304 #[test]
305 fn test_error_id_generation() {
306 let diagnostic1 = internal_with_context("error 1", "file1.rs", 10, 5, "func1", "mod1");
307
308 sleep(Duration::from_millis(2));
310
311 let diagnostic2 = internal_with_context("error 2", "file2.rs", 20, 10, "func2", "mod2");
312
313 let id1 = diagnostic1.message.split('[').nth(1).unwrap().split(']').nth(0).unwrap();
315 let id2 = diagnostic2.message.split('[').nth(1).unwrap().split(']').nth(0).unwrap();
316
317 assert_ne!(id1, id2);
319 assert!(id1.starts_with("ERR-"));
320 assert!(id2.starts_with("ERR-"));
321 }
322}