Skip to main content

vortex_error/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4#![deny(missing_docs)]
5
6//! This crate defines error & result types for Vortex.
7//! It also contains a variety of useful macros for error handling.
8
9use std::backtrace::Backtrace;
10use std::backtrace::BacktraceStatus;
11use std::borrow::Cow;
12use std::convert::Infallible;
13use std::env;
14use std::error::Error;
15use std::fmt;
16use std::fmt::Debug;
17use std::fmt::Display;
18use std::fmt::Formatter;
19use std::io;
20use std::num::TryFromIntError;
21use std::ops::Deref;
22use std::sync::Arc;
23use std::sync::LazyLock;
24
25/// A string that can be used as an error message.
26#[derive(Debug)]
27pub struct ErrString(Cow<'static, str>);
28
29#[expect(
30    clippy::fallible_impl_from,
31    reason = "intentionally panic in debug mode when VORTEX_PANIC_ON_ERR is set"
32)]
33impl<T> From<T> for ErrString
34where
35    T: Into<Cow<'static, str>>,
36{
37    #[expect(
38        clippy::panic,
39        reason = "intentionally panic in debug mode when VORTEX_PANIC_ON_ERR is set"
40    )]
41    fn from(msg: T) -> Self {
42        if panic_on_err() {
43            panic!("{}\nBacktrace:\n{}", msg.into(), Backtrace::capture());
44        } else {
45            Self(msg.into())
46        }
47    }
48}
49
50fn panic_on_err() -> bool {
51    static PANIC_ON_ERR: LazyLock<bool> =
52        LazyLock::new(|| env::var("VORTEX_PANIC_ON_ERR").is_ok_and(|v| v == "1"));
53    *PANIC_ON_ERR
54}
55
56impl AsRef<str> for ErrString {
57    fn as_ref(&self) -> &str {
58        &self.0
59    }
60}
61
62impl Deref for ErrString {
63    type Target = str;
64
65    fn deref(&self) -> &Self::Target {
66        &self.0
67    }
68}
69
70impl Display for ErrString {
71    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
72        Display::fmt(&self.0, f)
73    }
74}
75
76impl From<Infallible> for VortexError {
77    fn from(_: Infallible) -> Self {
78        unreachable!()
79    }
80}
81
82const _: () = {
83    assert!(size_of::<VortexError>() < 128);
84};
85
86/// The top-level error type for Vortex.
87#[non_exhaustive]
88pub enum VortexError {
89    /// A catch-all error variant
90    Other(ErrString, Box<Backtrace>),
91    /// A wrapped external error
92    External(Box<dyn Error + Send + Sync + 'static>, Box<Backtrace>),
93    /// An index is out of bounds.
94    OutOfBounds(usize, usize, usize, Box<Backtrace>),
95    /// An error occurred while executing a compute kernel.
96    Compute(ErrString, Box<Backtrace>),
97    /// An invalid argument was provided.
98    InvalidArgument(ErrString, Box<Backtrace>),
99    /// An error occurred while serializing or deserializing.
100    Serde(ErrString, Box<Backtrace>),
101    /// An unimplemented function was called.
102    NotImplemented(ErrString, ErrString, Box<Backtrace>),
103    /// A type mismatch occurred.
104    MismatchedTypes(ErrString, ErrString, Box<Backtrace>),
105    /// An assertion failed.
106    AssertionFailed(ErrString, Box<Backtrace>),
107    /// A wrapper for other errors, carrying additional context.
108    Context(ErrString, Box<VortexError>),
109    /// A wrapper for shared errors that require cloning.
110    Shared(Arc<VortexError>),
111    /// A wrapper for errors from the Arrow library.
112    Arrow(arrow_schema::ArrowError, Box<Backtrace>),
113    /// A wrapper for errors from the FlatBuffers library.
114    #[cfg(feature = "flatbuffers")]
115    FlatBuffers(flatbuffers::InvalidFlatbuffer, Box<Backtrace>),
116    /// A wrapper for formatting errors.
117    Fmt(fmt::Error, Box<Backtrace>),
118    /// A wrapper for IO errors.
119    Io(io::Error, Box<Backtrace>),
120    /// A wrapper for errors from the Object Store library.
121    #[cfg(feature = "object_store")]
122    ObjectStore(object_store::Error, Box<Backtrace>),
123    /// A wrapper for errors from the Jiff library.
124    Jiff(jiff::Error, Box<Backtrace>),
125    /// A wrapper for Tokio join error.
126    #[cfg(feature = "tokio")]
127    Join(tokio::task::JoinError, Box<Backtrace>),
128    /// Wrap errors for fallible integer casting.
129    TryFromInt(TryFromIntError, Box<Backtrace>),
130    /// Wrap protobuf-related errors
131    Prost(Box<dyn Error + Send + Sync + 'static>, Box<Backtrace>),
132}
133
134impl VortexError {
135    /// Adds additional context to an error.
136    pub fn with_context<T: Into<ErrString>>(self, msg: T) -> Self {
137        VortexError::Context(msg.into(), Box::new(self))
138    }
139
140    /// Error prefix by variant
141    fn variant_prefix(&self) -> &'static str {
142        use VortexError::*;
143
144        match self {
145            Other(..) => "Other error: ",
146            External(..) => "External error: ",
147            OutOfBounds(..) => "Out of bounds error: ",
148            Compute(..) => "Compute error: ",
149            InvalidArgument(..) => "Invalid argument error: ",
150            Serde(..) => "Serde error: ",
151            NotImplemented(..) => "Not implemented error: ",
152            MismatchedTypes(..) => "Mismatched types error: ",
153            AssertionFailed(..) => "Assertion failed error: ",
154            Context(..) | Shared(..) => "", // basically delegate to the underlying one
155            Arrow(..) => "Arrow error: ",
156            #[cfg(feature = "flatbuffers")]
157            FlatBuffers(..) => "Flat buffers error: ",
158            Fmt(..) => "Fmt: ",
159            Io(..) => "Io: ",
160            #[cfg(feature = "object_store")]
161            ObjectStore(..) => "Object store error: ",
162            Jiff(..) => "Jiff error: ",
163            #[cfg(feature = "tokio")]
164            Join(..) => "Tokio join error:",
165            TryFromInt(..) => "Try from int error:",
166            Prost(..) => "Prost error:",
167        }
168    }
169
170    fn backtrace(&self) -> Option<&Backtrace> {
171        use VortexError::*;
172
173        match self {
174            Other(.., bt) => Some(bt.as_ref()),
175            External(.., bt) => Some(bt.as_ref()),
176            OutOfBounds(.., bt) => Some(bt.as_ref()),
177            Compute(.., bt) => Some(bt.as_ref()),
178            InvalidArgument(.., bt) => Some(bt.as_ref()),
179            Serde(.., bt) => Some(bt.as_ref()),
180            NotImplemented(.., bt) => Some(bt.as_ref()),
181            MismatchedTypes(.., bt) => Some(bt.as_ref()),
182            AssertionFailed(.., bt) => Some(bt.as_ref()),
183            Arrow(.., bt) => Some(bt.as_ref()),
184            #[cfg(feature = "flatbuffers")]
185            FlatBuffers(.., bt) => Some(bt.as_ref()),
186            Fmt(.., bt) => Some(bt.as_ref()),
187            Io(.., bt) => Some(bt.as_ref()),
188            #[cfg(feature = "object_store")]
189            ObjectStore(.., bt) => Some(bt.as_ref()),
190            Jiff(.., bt) => Some(bt.as_ref()),
191            #[cfg(feature = "tokio")]
192            Join(.., bt) => Some(bt.as_ref()),
193            TryFromInt(.., bt) => Some(bt.as_ref()),
194            Prost(.., bt) => Some(bt.as_ref()),
195            Context(_, inner) => inner.backtrace(),
196            Shared(inner) => inner.backtrace(),
197        }
198    }
199
200    fn message(&self) -> String {
201        use VortexError::*;
202
203        match self {
204            Other(msg, _) => msg.to_string(),
205            External(err, _) => err.to_string(),
206            OutOfBounds(idx, start, stop, _) => {
207                format!("index {idx} out of bounds from {start} to {stop}")
208            }
209            Compute(msg, _) | InvalidArgument(msg, _) | Serde(msg, _) | AssertionFailed(msg, _) => {
210                format!("{msg}")
211            }
212            NotImplemented(func, by_whom, _) => {
213                format!("function {func} not implemented for {by_whom}")
214            }
215            MismatchedTypes(expected, actual, _) => {
216                format!("expected type: {expected} but instead got {actual}")
217            }
218            Context(msg, inner) => {
219                format!("{msg}:\n  {inner}")
220            }
221            Shared(inner) => inner.message(),
222            Arrow(err, _) => {
223                format!("{err}")
224            }
225            #[cfg(feature = "flatbuffers")]
226            FlatBuffers(err, _) => {
227                format!("{err}")
228            }
229            Fmt(err, _) => {
230                format!("{err}")
231            }
232            Io(err, _) => {
233                format!("{err}")
234            }
235            #[cfg(feature = "object_store")]
236            ObjectStore(err, _) => {
237                format!("{err}")
238            }
239            Jiff(err, _) => {
240                format!("{err}")
241            }
242            #[cfg(feature = "tokio")]
243            Join(err, _) => {
244                format!("{err}")
245            }
246            TryFromInt(err, _) => {
247                format!("{err}")
248            }
249            Prost(err, _) => {
250                format!("{err}")
251            }
252        }
253    }
254}
255
256impl Display for VortexError {
257    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
258        write!(f, "{}", self.variant_prefix())?;
259        write!(f, "{}", self.message())?;
260        if let Some(backtrace) = self.backtrace()
261            && backtrace.status() == BacktraceStatus::Captured
262        {
263            write!(f, "\nBacktrace:\n{backtrace}")?;
264        }
265
266        Ok(())
267    }
268}
269
270impl Debug for VortexError {
271    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
272        write!(f, "{self}")
273    }
274}
275
276impl Error for VortexError {
277    fn source(&self) -> Option<&(dyn Error + 'static)> {
278        use VortexError::*;
279
280        match self {
281            External(err, _) => Some(err.as_ref()),
282            Context(_, inner) => inner.source(),
283            Shared(inner) => inner.source(),
284            Arrow(err, _) => Some(err),
285            #[cfg(feature = "flatbuffers")]
286            FlatBuffers(err, _) => Some(err),
287            Io(err, _) => Some(err),
288            #[cfg(feature = "object_store")]
289            ObjectStore(err, _) => Some(err),
290            Jiff(err, _) => Some(err),
291            #[cfg(feature = "tokio")]
292            Join(err, _) => Some(err),
293            Prost(err, _) => Some(err.as_ref()),
294            _ => None,
295        }
296    }
297}
298
299/// A type alias for Results that return VortexErrors as their error type.
300pub type VortexResult<T> = Result<T, VortexError>;
301
302/// A vortex result that can be shared or cloned.
303pub type SharedVortexResult<T> = Result<T, Arc<VortexError>>;
304
305impl From<Arc<VortexError>> for VortexError {
306    fn from(value: Arc<VortexError>) -> Self {
307        Self::from(&value)
308    }
309}
310
311impl From<&Arc<VortexError>> for VortexError {
312    fn from(e: &Arc<VortexError>) -> Self {
313        if let VortexError::Shared(e_inner) = e.as_ref() {
314            // don't re-wrap
315            VortexError::Shared(Arc::clone(e_inner))
316        } else {
317            VortexError::Shared(Arc::clone(e))
318        }
319    }
320}
321
322/// A trait for expect-ing a VortexResult or an Option.
323pub trait VortexExpect {
324    /// The type of the value being expected.
325    type Output;
326
327    /// Returns the value of the result if it is Ok, otherwise panics with the error.
328    /// Should be called only in contexts where the error condition represents a bug (programmer error).
329    ///
330    /// # `&'static` message lifetime
331    ///
332    /// The panic string argument should be a string literal, hence the `&'static` lifetime. If
333    /// you'd like to panic with a dynamic format string, consider using `unwrap_or_else` combined
334    /// with the `vortex_panic!` macro instead.
335    fn vortex_expect(self, msg: &'static str) -> Self::Output;
336}
337
338impl<T, E> VortexExpect for Result<T, E>
339where
340    E: Into<VortexError>,
341{
342    type Output = T;
343
344    #[inline(always)]
345    fn vortex_expect(self, msg: &'static str) -> Self::Output {
346        self.map_err(|err| err.into())
347            .unwrap_or_else(|e| vortex_panic!(e.with_context(msg.to_string())))
348    }
349}
350
351impl<T> VortexExpect for Option<T> {
352    type Output = T;
353
354    #[inline(always)]
355    fn vortex_expect(self, msg: &'static str) -> Self::Output {
356        self.unwrap_or_else(|| {
357            let err = VortexError::AssertionFailed(
358                msg.to_string().into(),
359                Box::new(Backtrace::capture()),
360            );
361            vortex_panic!(err)
362        })
363    }
364}
365
366/// A convenient macro for creating a VortexError.
367#[macro_export]
368macro_rules! vortex_err {
369    (Other: $($tts:tt)*) => {{
370        use std::backtrace::Backtrace;
371        let err_string = format!($($tts)*);
372        $crate::__private::must_use(
373            $crate::VortexError::Other(err_string.into(), Box::new(Backtrace::capture()))
374        )
375    }};
376    (AssertionFailed: $($tts:tt)*) => {{
377        use std::backtrace::Backtrace;
378        let err_string = format!($($tts)*);
379        $crate::__private::must_use(
380            $crate::VortexError::AssertionFailed(err_string.into(), Box::new(Backtrace::capture()))
381        )
382    }};
383    (IOError: $($tts:tt)*) => {{
384        use std::backtrace::Backtrace;
385        $crate::__private::must_use(
386            $crate::VortexError::IOError(err_string.into(), Box::new(Backtrace::capture()))
387        )
388    }};
389    (OutOfBounds: $idx:expr, $start:expr, $stop:expr) => {{
390        use std::backtrace::Backtrace;
391        $crate::__private::must_use(
392            $crate::VortexError::OutOfBounds($idx, $start, $stop, Box::new(Backtrace::capture()))
393        )
394    }};
395    (NotImplemented: $func:expr, $by_whom:expr) => {{
396        use std::backtrace::Backtrace;
397        $crate::__private::must_use(
398            $crate::VortexError::NotImplemented($func.into(), format!("{}", $by_whom).into(), Box::new(Backtrace::capture()))
399        )
400    }};
401    (MismatchedTypes: $expected:literal, $actual:expr) => {{
402        use std::backtrace::Backtrace;
403        $crate::__private::must_use(
404            $crate::VortexError::MismatchedTypes($expected.into(), $actual.to_string().into(), Box::new(Backtrace::capture()))
405        )
406    }};
407    (MismatchedTypes: $expected:expr, $actual:expr) => {{
408        use std::backtrace::Backtrace;
409        $crate::__private::must_use(
410            $crate::VortexError::MismatchedTypes($expected.to_string().into(), $actual.to_string().into(), Box::new(Backtrace::capture()))
411        )
412    }};
413    (Context: $msg:literal, $err:expr) => {{
414        $crate::__private::must_use(
415            $crate::VortexError::Context($msg.into(), Box::new($err))
416        )
417    }};
418    (External: $err:expr) => {{
419        use std::backtrace::Backtrace;
420        $crate::__private::must_use(
421            $crate::VortexError::External($err.into(), Box::new(Backtrace::capture()))
422        )
423    }};
424    ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {{
425        use std::backtrace::Backtrace;
426        $crate::__private::must_use(
427            $crate::VortexError::$variant(format!($fmt, $($arg),*).into(), Box::new(Backtrace::capture()))
428        )
429    }};
430    ($variant:ident: $err:expr $(,)?) => {
431        $crate::__private::must_use(
432            $crate::VortexError::$variant($err)
433        )
434    };
435    ($fmt:literal $(, $arg:expr)* $(,)?) => {
436        $crate::vortex_err!(Other: $fmt, $($arg),*)
437    };
438}
439
440/// A convenience macro for returning a VortexError.
441#[macro_export]
442macro_rules! vortex_bail {
443    ($($tt:tt)+) => {
444        return Err($crate::vortex_err!($($tt)+))
445    };
446}
447
448/// A macro that mirrors `assert!` but instead of panicking on a failed condition,
449/// it will immediately return an erroneous `VortexResult` to the calling context.
450#[macro_export]
451macro_rules! vortex_ensure {
452    ($cond:expr) => {
453        vortex_ensure!($cond, AssertionFailed: "{}", stringify!($cond));
454    };
455    ($cond:expr, $($tt:tt)*) => {
456        if !$cond {
457            $crate::vortex_bail!($($tt)*);
458        }
459    };
460}
461
462/// A macro that mirrors `assert_eq!` but instead of panicking when left != right,
463/// it will immediately return an erroneous `VortexResult` to the calling context.
464#[macro_export]
465macro_rules! vortex_ensure_eq {
466    ($left:expr, $right:expr) => {
467        $crate::vortex_ensure_eq!($left, $right, AssertionFailed: "{} != {}: {:?} != {:?}", stringify!($left), stringify!($right), $left, $right);
468    };
469    ($left:expr, $right:expr, $($tt:tt)*) => {
470        if $left != $right {
471            $crate::vortex_bail!($($tt)*);
472        }
473    };
474}
475
476/// A convenient macro for panicking with a VortexError in the presence of a programmer error
477/// (e.g., an invariant has been violated).
478#[macro_export]
479macro_rules! vortex_panic {
480    (OutOfBounds: $idx:expr, $start:expr, $stop:expr) => {{
481        $crate::vortex_panic!($crate::vortex_err!(OutOfBounds: $idx, $start, $stop))
482    }};
483    (NotImplemented: $func:expr, $for_whom:expr) => {{
484        $crate::vortex_panic!($crate::vortex_err!(NotImplemented: $func, $for_whom))
485    }};
486    (MismatchedTypes: $expected:literal, $actual:expr) => {{
487        $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
488    }};
489    (MismatchedTypes: $expected:expr, $actual:expr) => {{
490        $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
491    }};
492    (Context: $msg:literal, $err:expr) => {{
493        $crate::vortex_panic!($crate::vortex_err!(Context: $msg, $err))
494    }};
495    ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
496        $crate::vortex_panic!($crate::vortex_err!($variant: $fmt, $($arg),*))
497    };
498    ($err:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{
499        let err: $crate::VortexError = $err;
500        panic!("{}", err.with_context(format!($fmt, $($arg),*)))
501    }};
502    ($fmt:literal $(, $arg:expr)* $(,)?) => {
503        $crate::vortex_panic!($crate::vortex_err!($fmt, $($arg),*))
504    };
505    ($err:expr) => {{
506        let err: $crate::VortexError = $err;
507        panic!("{}", err)
508    }};
509}
510
511impl From<arrow_schema::ArrowError> for VortexError {
512    fn from(value: arrow_schema::ArrowError) -> Self {
513        VortexError::Arrow(value, Box::new(Backtrace::capture()))
514    }
515}
516
517#[cfg(feature = "flatbuffers")]
518impl From<flatbuffers::InvalidFlatbuffer> for VortexError {
519    fn from(value: flatbuffers::InvalidFlatbuffer) -> Self {
520        VortexError::FlatBuffers(value, Box::new(Backtrace::capture()))
521    }
522}
523
524impl From<io::Error> for VortexError {
525    fn from(value: io::Error) -> Self {
526        VortexError::Io(value, Box::new(Backtrace::capture()))
527    }
528}
529
530#[cfg(feature = "object_store")]
531impl From<object_store::Error> for VortexError {
532    fn from(value: object_store::Error) -> Self {
533        VortexError::ObjectStore(value, Box::new(Backtrace::capture()))
534    }
535}
536
537impl From<jiff::Error> for VortexError {
538    fn from(value: jiff::Error) -> Self {
539        VortexError::Jiff(value, Box::new(Backtrace::capture()))
540    }
541}
542
543#[cfg(feature = "tokio")]
544impl From<tokio::task::JoinError> for VortexError {
545    fn from(value: tokio::task::JoinError) -> Self {
546        if value.is_panic() {
547            std::panic::resume_unwind(value.into_panic())
548        } else {
549            VortexError::Join(value, Box::new(Backtrace::capture()))
550        }
551    }
552}
553
554impl From<TryFromIntError> for VortexError {
555    fn from(value: TryFromIntError) -> Self {
556        VortexError::TryFromInt(value, Box::new(Backtrace::capture()))
557    }
558}
559
560impl From<prost::EncodeError> for VortexError {
561    fn from(value: prost::EncodeError) -> Self {
562        Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
563    }
564}
565
566impl From<prost::DecodeError> for VortexError {
567    fn from(value: prost::DecodeError) -> Self {
568        Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
569    }
570}
571
572impl From<prost::UnknownEnumValue> for VortexError {
573    fn from(value: prost::UnknownEnumValue) -> Self {
574        Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
575    }
576}
577
578// Not public, referenced by macros only.
579#[doc(hidden)]
580pub mod __private {
581    #[doc(hidden)]
582    #[inline]
583    #[cold]
584    #[must_use]
585    pub const fn must_use(error: crate::VortexError) -> crate::VortexError {
586        error
587    }
588}