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
12mod ext;
13
14use std::backtrace::Backtrace;
15use std::borrow::Cow;
16use std::convert::Infallible;
17use std::error::Error;
18use std::fmt::{Debug, Display, Formatter};
19use std::num::TryFromIntError;
20use std::ops::Deref;
21use std::sync::{Arc, PoisonError};
22use std::{env, fmt, io};
23
24pub use ext::*;
25
26/// A string that can be used as an error message.
27#[derive(Debug)]
28pub struct ErrString(Cow<'static, str>);
29
30#[allow(clippy::fallible_impl_from)]
31impl<T> From<T> for ErrString
32where
33    T: Into<Cow<'static, str>>,
34{
35    #[allow(clippy::panic)]
36    fn from(msg: T) -> Self {
37        if env::var("VORTEX_PANIC_ON_ERR").as_deref().unwrap_or("") == "1" {
38            panic!("{}\nBacktrace:\n{}", msg.into(), Backtrace::capture());
39        } else {
40            Self(msg.into())
41        }
42    }
43}
44
45impl AsRef<str> for ErrString {
46    fn as_ref(&self) -> &str {
47        &self.0
48    }
49}
50
51impl Deref for ErrString {
52    type Target = str;
53
54    fn deref(&self) -> &Self::Target {
55        &self.0
56    }
57}
58
59impl Display for ErrString {
60    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
61        Display::fmt(&self.0, f)
62    }
63}
64
65impl From<Infallible> for VortexError {
66    fn from(_: Infallible) -> Self {
67        unreachable!()
68    }
69}
70
71const _: () = {
72    assert!(size_of::<VortexError>() < 128);
73};
74/// The top-level error type for Vortex.
75#[non_exhaustive]
76pub enum VortexError {
77    /// A wrapped generic error
78    Generic(Box<dyn Error + Send + Sync + 'static>, Box<Backtrace>),
79    /// An index is out of bounds.
80    OutOfBounds(usize, usize, usize, Box<Backtrace>),
81    /// An error occurred while executing a compute kernel.
82    ComputeError(ErrString, Box<Backtrace>),
83    /// An invalid argument was provided.
84    InvalidArgument(ErrString, Box<Backtrace>),
85    /// The system has reached an invalid state,
86    InvalidState(ErrString, Box<Backtrace>),
87    /// An error occurred while serializing or deserializing.
88    InvalidSerde(ErrString, Box<Backtrace>),
89    /// An unimplemented function was called.
90    NotImplemented(ErrString, ErrString, Box<Backtrace>),
91    /// A type mismatch occurred.
92    MismatchedTypes(ErrString, ErrString, Box<Backtrace>),
93    /// An assertion failed.
94    AssertionFailed(ErrString, Box<Backtrace>),
95    /// A wrapper for other errors, carrying additional context.
96    Context(ErrString, Box<VortexError>),
97    /// A wrapper for shared errors that require cloning.
98    Shared(Arc<VortexError>),
99    /// A wrapper for errors from the Arrow library.
100    ArrowError(arrow_schema::ArrowError, Box<Backtrace>),
101    /// A wrapper for errors from the FlatBuffers library.
102    #[cfg(feature = "flatbuffers")]
103    FlatBuffersError(flatbuffers::InvalidFlatbuffer, Box<Backtrace>),
104    /// A wrapper for formatting errors.
105    FmtError(fmt::Error, Box<Backtrace>),
106    /// A wrapper for IO errors.
107    IOError(io::Error, Box<Backtrace>),
108    /// A wrapper for UTF-8 conversion errors.
109    Utf8Error(std::str::Utf8Error, Box<Backtrace>),
110    /// A wrapper for errors from the Parquet library.
111    #[cfg(feature = "parquet")]
112    ParquetError(parquet::errors::ParquetError, Box<Backtrace>),
113    /// A wrapper for errors from the standard library when converting a slice to an array.
114    TryFromSliceError(std::array::TryFromSliceError, Box<Backtrace>),
115    /// A wrapper for errors from the Object Store library.
116    #[cfg(feature = "object_store")]
117    ObjectStore(object_store::Error, Box<Backtrace>),
118    /// A wrapper for errors from the Jiff library.
119    JiffError(jiff::Error, Box<Backtrace>),
120    /// A wrapper for Tokio join error.
121    #[cfg(feature = "tokio")]
122    JoinError(tokio::task::JoinError, Box<Backtrace>),
123    /// A wrapper for URL parsing errors.
124    UrlError(url::ParseError, Box<Backtrace>),
125    /// Wrap errors for fallible integer casting.
126    TryFromInt(TryFromIntError, Box<Backtrace>),
127    /// Wrap serde and serde json errors
128    #[cfg(feature = "serde")]
129    SerdeJsonError(serde_json::Error, Box<Backtrace>),
130    /// Wrap prost encode error
131    #[cfg(feature = "prost")]
132    ProstEncodeError(prost::EncodeError, Box<Backtrace>),
133    /// Wrap prost decode error
134    #[cfg(feature = "prost")]
135    ProstDecodeError(prost::DecodeError, Box<Backtrace>),
136    /// Wrap prost unknown enum value
137    #[cfg(feature = "prost")]
138    ProstUnknownEnumValue(prost::UnknownEnumValue, Box<Backtrace>),
139}
140
141impl VortexError {
142    /// Adds additional context to an error.
143    pub fn with_context<T: Into<ErrString>>(self, msg: T) -> Self {
144        VortexError::Context(msg.into(), Box::new(self))
145    }
146}
147
148impl Display for VortexError {
149    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
150        match self {
151            VortexError::Generic(err, backtrace) => {
152                write!(f, "{err}\nBacktrace:\n{backtrace}")
153            }
154            VortexError::OutOfBounds(idx, start, stop, backtrace) => {
155                write!(
156                    f,
157                    "index {idx} out of bounds from {start} to {stop}\nBacktrace:\n{backtrace}",
158                )
159            }
160            VortexError::ComputeError(msg, backtrace) => {
161                write!(f, "{msg}\nBacktrace:\n{backtrace}")
162            }
163            VortexError::InvalidArgument(msg, backtrace) => {
164                write!(f, "{msg}\nBacktrace:\n{backtrace}")
165            }
166            VortexError::InvalidState(msg, backtrace) => {
167                write!(f, "{msg}\nBacktrace:\n{backtrace}")
168            }
169            VortexError::InvalidSerde(msg, backtrace) => {
170                write!(f, "{msg}\nBacktrace:\n{backtrace}")
171            }
172            VortexError::NotImplemented(func, by_whom, backtrace) => {
173                write!(
174                    f,
175                    "function {func} not implemented for {by_whom}\nBacktrace:\n{backtrace}",
176                )
177            }
178            VortexError::MismatchedTypes(expected, actual, backtrace) => {
179                write!(
180                    f,
181                    "expected type: {expected} but instead got {actual}\nBacktrace:\n{backtrace}",
182                )
183            }
184            VortexError::AssertionFailed(msg, backtrace) => {
185                write!(f, "{msg}\nBacktrace:\n{backtrace}")
186            }
187            VortexError::Context(msg, inner) => {
188                write!(f, "{msg}: {inner}")
189            }
190            VortexError::Shared(inner) => Display::fmt(inner, f),
191            VortexError::ArrowError(err, backtrace) => {
192                write!(f, "{err}\nBacktrace:\n{backtrace}")
193            }
194            #[cfg(feature = "flatbuffers")]
195            VortexError::FlatBuffersError(err, backtrace) => {
196                write!(f, "{err}\nBacktrace:\n{backtrace}")
197            }
198            VortexError::FmtError(err, backtrace) => {
199                write!(f, "{err}\nBacktrace:\n{backtrace}")
200            }
201            VortexError::IOError(err, backtrace) => {
202                write!(f, "{err}\nBacktrace:\n{backtrace}")
203            }
204            VortexError::Utf8Error(err, backtrace) => {
205                write!(f, "{err}\nBacktrace:\n{backtrace}")
206            }
207            #[cfg(feature = "parquet")]
208            VortexError::ParquetError(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::FmtError(err, _) => Some(err),
261            VortexError::IOError(err, _) => Some(err),
262            VortexError::Utf8Error(err, _) => Some(err),
263            #[cfg(feature = "parquet")]
264            VortexError::ParquetError(err, _) => Some(err),
265            VortexError::TryFromSliceError(err, _) => Some(err),
266            #[cfg(feature = "object_store")]
267            VortexError::ObjectStore(err, _) => Some(err),
268            VortexError::JiffError(err, _) => Some(err),
269            #[cfg(feature = "tokio")]
270            VortexError::JoinError(err, _) => Some(err),
271            VortexError::UrlError(err, _) => Some(err),
272            VortexError::TryFromInt(err, _) => Some(err),
273            #[cfg(feature = "serde")]
274            VortexError::SerdeJsonError(err, _) => Some(err),
275            #[cfg(feature = "prost")]
276            VortexError::ProstEncodeError(err, _) => Some(err),
277            #[cfg(feature = "prost")]
278            VortexError::ProstDecodeError(err, _) => Some(err),
279            #[cfg(feature = "prost")]
280            VortexError::ProstUnknownEnumValue(err, _) => Some(err),
281            _ => None,
282        }
283    }
284}
285
286impl Debug for VortexError {
287    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
288        Display::fmt(self, f)
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 unwrapping a VortexResult.
316pub trait VortexUnwrap {
317    /// The type of the value being unwrapped.
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    fn vortex_unwrap(self) -> Self::Output;
323}
324
325impl<T, E> VortexUnwrap for Result<T, E>
326where
327    E: Into<VortexError>,
328{
329    type Output = T;
330
331    #[inline(always)]
332    fn vortex_unwrap(self) -> Self::Output {
333        self.map_err(|err| err.into())
334            .unwrap_or_else(|err| vortex_panic!(err))
335    }
336}
337
338/// A trait for expect-ing a VortexResult or an Option.
339pub trait VortexExpect {
340    /// The type of the value being expected.
341    type Output;
342
343    /// Returns the value of the result if it is Ok, otherwise panics with the error.
344    /// Should be called only in contexts where the error condition represents a bug (programmer error).
345    fn vortex_expect(self, msg: &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: &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: &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 convenient macro for panicking with a VortexError in the presence of a programmer error
446/// (e.g., an invariant has been violated).
447#[macro_export]
448macro_rules! vortex_panic {
449    (OutOfBounds: $idx:expr, $start:expr, $stop:expr) => {{
450        $crate::vortex_panic!($crate::vortex_err!(OutOfBounds: $idx, $start, $stop))
451    }};
452    (NotImplemented: $func:expr, $for_whom:expr) => {{
453        $crate::vortex_panic!($crate::vortex_err!(NotImplemented: $func, $for_whom))
454    }};
455    (MismatchedTypes: $expected:literal, $actual:expr) => {{
456        $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
457    }};
458    (MismatchedTypes: $expected:expr, $actual:expr) => {{
459        $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
460    }};
461    (Context: $msg:literal, $err:expr) => {{
462        $crate::vortex_panic!($crate::vortex_err!(Context: $msg, $err))
463    }};
464    ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
465        $crate::vortex_panic!($crate::vortex_err!($variant: $fmt, $($arg),*))
466    };
467    ($err:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{
468        let err: $crate::VortexError = $err;
469        panic!("{}", err.with_context(format!($fmt, $($arg),*)))
470    }};
471    ($fmt:literal $(, $arg:expr)* $(,)?) => {
472        $crate::vortex_panic!($crate::vortex_err!($fmt, $($arg),*))
473    };
474    ($err:expr) => {{
475        let err: $crate::VortexError = $err;
476        panic!("{}", err)
477    }};
478}
479
480impl From<arrow_schema::ArrowError> for VortexError {
481    fn from(value: arrow_schema::ArrowError) -> Self {
482        VortexError::ArrowError(value, Box::new(Backtrace::capture()))
483    }
484}
485
486#[cfg(feature = "flatbuffers")]
487impl From<flatbuffers::InvalidFlatbuffer> for VortexError {
488    fn from(value: flatbuffers::InvalidFlatbuffer) -> Self {
489        VortexError::FlatBuffersError(value, Box::new(Backtrace::capture()))
490    }
491}
492
493impl From<fmt::Error> for VortexError {
494    fn from(value: fmt::Error) -> Self {
495        VortexError::FmtError(value, Box::new(Backtrace::capture()))
496    }
497}
498
499impl From<io::Error> for VortexError {
500    fn from(value: io::Error) -> Self {
501        VortexError::IOError(value, Box::new(Backtrace::capture()))
502    }
503}
504
505impl From<std::str::Utf8Error> for VortexError {
506    fn from(value: std::str::Utf8Error) -> Self {
507        VortexError::Utf8Error(value, Box::new(Backtrace::capture()))
508    }
509}
510
511#[cfg(feature = "parquet")]
512impl From<parquet::errors::ParquetError> for VortexError {
513    fn from(value: parquet::errors::ParquetError) -> Self {
514        VortexError::ParquetError(value, Box::new(Backtrace::capture()))
515    }
516}
517
518impl From<std::array::TryFromSliceError> for VortexError {
519    fn from(value: std::array::TryFromSliceError) -> Self {
520        VortexError::TryFromSliceError(value, Box::new(Backtrace::capture()))
521    }
522}
523
524#[cfg(feature = "object_store")]
525impl From<object_store::Error> for VortexError {
526    fn from(value: object_store::Error) -> Self {
527        VortexError::ObjectStore(value, Box::new(Backtrace::capture()))
528    }
529}
530
531impl From<jiff::Error> for VortexError {
532    fn from(value: jiff::Error) -> Self {
533        VortexError::JiffError(value, Box::new(Backtrace::capture()))
534    }
535}
536
537#[cfg(feature = "tokio")]
538impl From<tokio::task::JoinError> for VortexError {
539    fn from(value: tokio::task::JoinError) -> Self {
540        VortexError::JoinError(value, Box::new(Backtrace::capture()))
541    }
542}
543
544impl From<url::ParseError> for VortexError {
545    fn from(value: url::ParseError) -> Self {
546        VortexError::UrlError(value, Box::new(Backtrace::capture()))
547    }
548}
549
550impl From<TryFromIntError> for VortexError {
551    fn from(value: TryFromIntError) -> Self {
552        VortexError::TryFromInt(value, Box::new(Backtrace::capture()))
553    }
554}
555
556#[cfg(feature = "serde")]
557impl From<serde_json::Error> for VortexError {
558    fn from(value: serde_json::Error) -> Self {
559        VortexError::SerdeJsonError(value, Box::new(Backtrace::capture()))
560    }
561}
562
563#[cfg(feature = "prost")]
564impl From<prost::EncodeError> for VortexError {
565    fn from(value: prost::EncodeError) -> Self {
566        VortexError::ProstEncodeError(value, Box::new(Backtrace::capture()))
567    }
568}
569
570#[cfg(feature = "prost")]
571impl From<prost::DecodeError> for VortexError {
572    fn from(value: prost::DecodeError) -> Self {
573        VortexError::ProstDecodeError(value, Box::new(Backtrace::capture()))
574    }
575}
576
577#[cfg(feature = "prost")]
578impl From<prost::UnknownEnumValue> for VortexError {
579    fn from(value: prost::UnknownEnumValue) -> Self {
580        VortexError::ProstUnknownEnumValue(value, Box::new(Backtrace::capture()))
581    }
582}
583
584// Not public, referenced by macros only.
585#[doc(hidden)]
586pub mod __private {
587    #[doc(hidden)]
588    #[inline]
589    #[cold]
590    #[must_use]
591    pub const fn must_use(error: crate::VortexError) -> crate::VortexError {
592        error
593    }
594}
595
596impl<T> From<PoisonError<T>> for VortexError {
597    fn from(_value: PoisonError<T>) -> Self {
598        // We don't include the value since it may be sensitive.
599        Self::InvalidState("Lock poisoned".into(), Box::new(Backtrace::capture()))
600    }
601}