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