Skip to main content

reifydb_core/error/diagnostic/
internal.rs

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