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