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 reifydb_runtime::clock::Clock;
5use reifydb_type::{error::diagnostic::Diagnostic, fragment::Fragment};
6
7/// Creates a detailed internal error diagnostic with source location and
8/// context
9pub 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	// Generate a unique error ID based on timestamp and location
20	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
72/// Simplified internal error without detailed context
73pub fn internal(reason: impl Into<String>) -> Diagnostic {
74	internal_with_context(reason, "unknown", 0, 0, "unknown", "unknown")
75}
76
77/// Creates a diagnostic for shutdown-related errors
78/// Used when operations fail because a subsystem is shutting down
79pub 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 to create an internal error with automatic source location capture
104#[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 to create an internal error result with automatic source location
143/// capture
144#[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 to create an internal error result with automatic source location
155/// capture
156#[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 to return an internal error with automatic source location capture
167/// This combines return_error! and internal_error! for convenience
168#[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		// Small delay to ensure different timestamps
309		sleep(Duration::from_millis(2));
310
311		let diagnostic2 = internal_with_context("error 2", "file2.rs", 20, 10, "func2", "mod2");
312
313		// Extract error IDs from messages
314		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		// Error IDs should be unique
318		assert_ne!(id1, id2);
319		assert!(id1.starts_with("ERR-"));
320		assert!(id2.starts_with("ERR-"));
321	}
322}