Skip to main content

surrealdb_common/error/
mod.rs

1use core::fmt;
2use std::fmt::{Debug, Display};
3use std::ops::{Deref, DerefMut};
4use std::ptr::NonNull;
5use std::result::Result as StdResult;
6
7mod code;
8mod engine;
9mod leaf;
10mod raw;
11pub mod source;
12
13pub use code::ErrorCode;
14pub use engine::EngineError;
15pub use leaf::{LeafError, internal_todo};
16use raw::{RawError, RawTypedError};
17
18pub trait ErrorTrait: Display + Debug + 'static {
19	fn error_code(&self) -> ErrorCode {
20		ErrorCode::default()
21	}
22}
23
24impl<E: std::error::Error + 'static> ErrorTrait for E {}
25
26pub type Result<T, E = Error> = StdResult<T, E>;
27
28/// Generic error type, optimized to have little overhead on the happy path.
29///
30/// This error will always be the size of a pointer, regardless of the errors it might contain.
31pub struct Error(RawError);
32
33impl Error {
34	/// Create a new error.
35	#[cold]
36	pub fn new<E>(e: E) -> Self
37	where
38		E: ErrorTrait,
39	{
40		Error(RawError::new(e))
41	}
42
43	/// Returns the error code for the error.
44	pub fn error_code(&self) -> ErrorCode {
45		self.0.error_code()
46	}
47
48	/// Obtain a reference to the internal error if the error is of the right type.
49	pub fn downcast_ref<T: ErrorTrait>(&self) -> Option<&T> {
50		self.0.is::<T>().then(|| unsafe { self.0.unchecked_ref() })
51	}
52
53	/// Obtain a mutable reference to the internal error if the error is of the right type.
54	pub fn downcast_mut<T: ErrorTrait>(&mut self) -> Option<&mut T> {
55		self.0.is::<T>().then(|| unsafe { self.0.unchecked_mut() })
56	}
57
58	/// Convert value to the internal error if the error is of the right type.
59	pub fn into_inner<T: ErrorTrait>(self) -> Result<T, Self> {
60		if self.0.is::<T>() {
61			Ok(unsafe { self.0.unchecked_into_inner() })
62		} else {
63			Err(self)
64		}
65	}
66
67	/// Returns a typed version of the error if the error is of the right type.
68	pub fn downcast<T: ErrorTrait>(self) -> Result<TypedError<T>, Self> {
69		if self.0.is::<T>() {
70			Ok(TypedError(unsafe { self.0.unchecked_cast() }))
71		} else {
72			Err(self)
73		}
74	}
75}
76
77impl fmt::Debug for Error {
78	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79		self.0.debug(f)
80	}
81}
82
83impl fmt::Display for Error {
84	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85		self.0.display(f)
86	}
87}
88
89/// Error type, optimized to have little overhead on the happy path.
90///
91/// This error can be efficiently cast into [`Error`] without any allocation.
92///
93/// This error will always be the size of a pointer, regardless of the errors it might contain.
94pub struct TypedError<T: ErrorTrait>(RawTypedError<T>);
95
96impl<T: ErrorTrait> TypedError<T> {
97	/// Creates a new typed error. Boxing the given error.
98	pub fn new(e: T) -> Self {
99		TypedError(RawTypedError::new(e))
100	}
101
102	/// Convert the error into a type erased version.
103	pub fn erase(self) -> Error {
104		Error(self.0.erase())
105	}
106
107	/// Returns the underlying error
108	pub fn into_inner(self) -> T {
109		self.0.into_inner()
110	}
111
112	/// Returns a raw pointer to the error.
113	pub fn into_raw(self) -> NonNull<()> {
114		self.0.into_raw()
115	}
116
117	/// Create a type error from a pointer.
118	///
119	/// # Safety
120	/// Pointer must have previously been returned from [`TypedError::into_raw`] and after calling
121	/// from_raw the pointer must no longer be used.
122	pub unsafe fn from_raw(ptr: NonNull<()>) -> Self {
123		unsafe { TypedError(RawTypedError::from_raw(ptr)) }
124	}
125
126	/// Create a type error from a pointer.
127	///
128	/// # Safety
129	/// Pointer must have previously been returned from [`TypedError::into_raw`] and the pointer
130	/// must not be mutably accessed with for example [`TypedError::ref_mut_from_raw`].
131	pub unsafe fn ref_from_raw<'a>(ptr: NonNull<()>) -> &'a T {
132		unsafe { RawTypedError::<T>::ref_from_raw(ptr) }
133	}
134
135	/// Create a type error from a pointer.
136	///
137	/// # Safety
138	/// Pointer must have previously been returned from [`TypedError::into_raw`] and the pointer
139	/// must not be borrowed with for example [`TypedError::ref_from_raw`].
140	pub unsafe fn ref_mut_from_raw<'a>(ptr: NonNull<()>) -> &'a mut T {
141		unsafe { RawTypedError::<T>::ref_mut_from_raw(ptr) }
142	}
143}
144
145impl<T: ErrorTrait> Deref for TypedError<T> {
146	type Target = T;
147
148	fn deref(&self) -> &Self::Target {
149		self.0.deref()
150	}
151}
152
153impl<T: ErrorTrait> DerefMut for TypedError<T> {
154	fn deref_mut(&mut self) -> &mut Self::Target {
155		self.0.deref_mut()
156	}
157}
158
159impl<T: ErrorTrait> fmt::Debug for TypedError<T> {
160	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161		Debug::fmt(self.0.deref(), f)
162	}
163}
164
165impl<T: ErrorTrait> fmt::Display for TypedError<T> {
166	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167		Display::fmt(self.0.deref(), f)
168	}
169}
170
171#[cfg(test)]
172mod tests {
173	use std::fmt;
174	use std::sync::atomic::{AtomicUsize, Ordering};
175
176	use super::{Error, TypedError};
177
178	const SENTINEL: u64 = 0xDEAD_BEEF_CAFE_BABE;
179
180	/// Error type for regression tests covering the `Error`/`TypedError` ownership-transfer
181	/// paths (`erase`, `downcast`, `into_inner`).
182	///
183	/// Holds a `Box<u64>` so any double-free of the inner allocation also corrupts the heap
184	/// (caught by the system allocator and tools like Miri/ASan), and increments a counter on
185	/// `Drop` so the tests can assert that the inner value is dropped exactly once across each
186	/// conversion path.
187	struct DropCounted {
188		value: Box<u64>,
189		counter: &'static AtomicUsize,
190	}
191
192	impl DropCounted {
193		fn new(counter: &'static AtomicUsize) -> Self {
194			DropCounted {
195				value: Box::new(SENTINEL),
196				counter,
197			}
198		}
199	}
200
201	impl fmt::Debug for DropCounted {
202		fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203			write!(f, "DropCounted({:#x})", *self.value)
204		}
205	}
206
207	impl fmt::Display for DropCounted {
208		fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209			write!(f, "drop-counted error: {:#x}", *self.value)
210		}
211	}
212
213	impl std::error::Error for DropCounted {}
214
215	impl Drop for DropCounted {
216		fn drop(&mut self) {
217			self.counter.fetch_add(1, Ordering::SeqCst);
218		}
219	}
220
221	/// Unrelated error type used to exercise the `downcast` failure branch.
222	#[derive(Debug)]
223	struct OtherType;
224
225	impl fmt::Display for OtherType {
226		fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227			f.write_str("OtherType")
228		}
229	}
230
231	impl std::error::Error for OtherType {}
232
233	#[test]
234	fn error_new_format_then_drop_runs_once() {
235		static COUNTER: AtomicUsize = AtomicUsize::new(0);
236		{
237			let err = Error::new(DropCounted::new(&COUNTER));
238			let debug = format!("{:?}", err);
239			let display = format!("{}", err);
240			assert!(debug.contains("DropCounted"), "unexpected debug: {debug}");
241			assert!(display.contains("drop-counted error"), "unexpected display: {display}",);
242			assert_eq!(COUNTER.load(Ordering::SeqCst), 0, "value dropped early");
243		}
244		assert_eq!(COUNTER.load(Ordering::SeqCst), 1, "expected exactly one drop");
245	}
246
247	#[test]
248	fn error_downcast_then_into_inner_drops_once() {
249		static COUNTER: AtomicUsize = AtomicUsize::new(0);
250		{
251			let err = Error::new(DropCounted::new(&COUNTER));
252			let typed: TypedError<DropCounted> =
253				err.downcast::<DropCounted>().expect("downcast should succeed");
254			assert_eq!(*typed.value, SENTINEL);
255			let inner = typed.into_inner();
256			assert_eq!(*inner.value, SENTINEL);
257		}
258		assert_eq!(COUNTER.load(Ordering::SeqCst), 1, "expected exactly one drop");
259	}
260
261	#[test]
262	fn error_into_inner_drops_once() {
263		static COUNTER: AtomicUsize = AtomicUsize::new(0);
264		{
265			let err = Error::new(DropCounted::new(&COUNTER));
266			let inner = err.into_inner::<DropCounted>().expect("into_inner should succeed");
267			assert_eq!(*inner.value, SENTINEL);
268		}
269		assert_eq!(COUNTER.load(Ordering::SeqCst), 1, "expected exactly one drop");
270	}
271
272	#[test]
273	fn error_downcast_failure_preserves_original() {
274		static COUNTER: AtomicUsize = AtomicUsize::new(0);
275		{
276			let err = Error::new(DropCounted::new(&COUNTER));
277			let err =
278				err.downcast::<OtherType>().expect_err("downcast to unrelated type should fail");
279			let _ = format!("{:?}", err);
280			let _ = format!("{}", err);
281			assert_eq!(COUNTER.load(Ordering::SeqCst), 0, "value dropped during failed downcast",);
282		}
283		assert_eq!(COUNTER.load(Ordering::SeqCst), 1, "expected exactly one drop");
284	}
285
286	#[test]
287	fn typed_error_erase_then_drop_runs_once() {
288		static COUNTER: AtomicUsize = AtomicUsize::new(0);
289		{
290			let typed = TypedError::new(DropCounted::new(&COUNTER));
291			let err: Error = typed.erase();
292			let _ = format!("{:?}", err);
293		}
294		assert_eq!(COUNTER.load(Ordering::SeqCst), 1, "expected exactly one drop");
295	}
296
297	#[test]
298	fn typed_error_erase_then_downcast_into_inner_drops_once() {
299		static COUNTER: AtomicUsize = AtomicUsize::new(0);
300		{
301			let typed = TypedError::new(DropCounted::new(&COUNTER));
302			let err = typed.erase();
303			let typed_back = err.downcast::<DropCounted>().expect("downcast should succeed");
304			let inner = typed_back.into_inner();
305			assert_eq!(*inner.value, SENTINEL);
306		}
307		assert_eq!(COUNTER.load(Ordering::SeqCst), 1, "expected exactly one drop");
308	}
309}