Skip to main content

reifydb_core/error/diagnostic/
internal.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use 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		// Small delay to ensure different timestamps
294		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		// Extract error IDs from messages
299		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		// Error IDs should be unique
303		assert_ne!(id1, id2);
304		assert!(id1.starts_with("ERR-"));
305		assert!(id2.starts_with("ERR-"));
306	}
307}