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