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