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