Skip to main content

reifydb_core/error/diagnostic/
internal.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4use std::env;
5
6use reifydb_runtime::clock::Clock;
7use reifydb_type::{error::Diagnostic, fragment::Fragment};
8
9/// Creates a detailed internal error diagnostic with source location and
10/// context
11pub 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	// Generate a unique error ID based on timestamp and location
22	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
74/// Simplified internal error without detailed context
75pub fn internal(reason: impl Into<String>) -> Diagnostic {
76	internal_with_context(reason, "unknown", 0, 0, "unknown", "unknown")
77}
78
79/// Creates a diagnostic for shutdown-related errors
80/// Used when operations fail because a subsystem is shutting down
81pub 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 to create an internal error with automatic source location capture
106#[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 to create an internal error result with automatic source location
145/// capture
146#[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 to create an internal error result with automatic source location
157/// capture
158#[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 to return an internal error with automatic source location capture
169/// This combines return_error! and internal_error! for convenience
170#[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		// Small delay to ensure different timestamps
311		sleep(Duration::from_millis(2));
312
313		let diagnostic2 = internal_with_context("error 2", "file2.rs", 20, 10, "func2", "mod2");
314
315		// Extract error IDs from messages
316		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		// Error IDs should be unique
320		assert_ne!(id1, id2);
321		assert!(id1.starts_with("ERR-"));
322		assert!(id2.starts_with("ERR-"));
323	}
324}