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