1#![deny(missing_docs)]
5
6use 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#[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#[non_exhaustive]
86pub enum VortexError {
87 Other(ErrString, Box<Backtrace>),
89 External(Box<dyn Error + Send + Sync + 'static>, Box<Backtrace>),
91 OutOfBounds(usize, usize, usize, Box<Backtrace>),
93 Compute(ErrString, Box<Backtrace>),
95 InvalidArgument(ErrString, Box<Backtrace>),
97 Serde(ErrString, Box<Backtrace>),
99 NotImplemented(ErrString, ErrString, Box<Backtrace>),
101 MismatchedTypes(ErrString, ErrString, Box<Backtrace>),
103 AssertionFailed(ErrString, Box<Backtrace>),
105 Context(ErrString, Box<VortexError>),
107 Shared(Arc<VortexError>),
109 Arrow(arrow_schema::ArrowError, Box<Backtrace>),
111 #[cfg(feature = "flatbuffers")]
113 FlatBuffers(flatbuffers::InvalidFlatbuffer, Box<Backtrace>),
114 Fmt(fmt::Error, Box<Backtrace>),
116 Io(io::Error, Box<Backtrace>),
118 #[cfg(feature = "object_store")]
120 ObjectStore(object_store::Error, Box<Backtrace>),
121 Jiff(jiff::Error, Box<Backtrace>),
123 #[cfg(feature = "tokio")]
125 Join(tokio::task::JoinError, Box<Backtrace>),
126 TryFromInt(TryFromIntError, Box<Backtrace>),
128 Prost(Box<dyn Error + Send + Sync + 'static>, Box<Backtrace>),
130}
131
132impl VortexError {
133 pub fn with_context<T: Into<ErrString>>(self, msg: T) -> Self {
135 VortexError::Context(msg.into(), Box::new(self))
136 }
137
138 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(..) => "", 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
297pub type VortexResult<T> = Result<T, VortexError>;
299
300pub 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 VortexError::Shared(Arc::clone(e_inner))
314 } else {
315 VortexError::Shared(Arc::clone(e))
316 }
317 }
318}
319
320pub trait VortexExpect {
322 type Output;
324
325 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 #[allow(clippy::inline_always)]
343 #[inline(always)]
344 fn vortex_expect(self, msg: &'static str) -> Self::Output {
345 match self {
346 Ok(value) => value,
347 Err(err) => __private::result_expect_failed(err, msg),
348 }
349 }
350}
351
352impl<T> VortexExpect for Option<T> {
353 type Output = T;
354
355 #[allow(clippy::inline_always)]
356 #[inline(always)]
357 fn vortex_expect(self, msg: &'static str) -> Self::Output {
358 match self {
359 Some(value) => value,
360 None => __private::option_expect_failed(msg),
361 }
362 }
363}
364
365#[macro_export]
367macro_rules! vortex_err {
368 (Other: $($tts:tt)*) => {
369 $crate::__private::fmt_err($crate::VortexError::Other, &format_args!($($tts)*))
370 };
371 (AssertionFailed: $($tts:tt)*) => {
372 $crate::__private::fmt_err($crate::VortexError::AssertionFailed, &format_args!($($tts)*))
373 };
374 (IOError: $($tts:tt)*) => {{
375 use std::backtrace::Backtrace;
376 $crate::__private::must_use(
377 $crate::VortexError::IOError(err_string.into(), Box::new(Backtrace::capture()))
378 )
379 }};
380 (OutOfBounds: $idx:expr, $start:expr, $stop:expr) => {
381 $crate::__private::out_of_bounds($idx, $start, $stop)
382 };
383 (NotImplemented: $func:expr, $by_whom:expr) => {
384 $crate::__private::not_implemented($func, &format_args!("{}", $by_whom))
385 };
386 (MismatchedTypes: $expected:expr, $actual:expr) => {
387 $crate::__private::mismatched_types(&$expected, &$actual)
388 };
389 (Context: $msg:literal, $err:expr) => {
390 $crate::__private::context($msg, $err)
391 };
392 (External: $err:expr) => {
393 $crate::__private::external($err)
394 };
395 ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
396 $crate::__private::fmt_err($crate::VortexError::$variant, &format_args!($fmt $(, $arg)*))
397 };
398 ($variant:ident: $err:expr $(,)?) => {
399 $crate::__private::must_use(
400 $crate::VortexError::$variant($err)
401 )
402 };
403 ($fmt:literal $(, $arg:expr)* $(,)?) => {
404 $crate::vortex_err!(Other: $fmt, $($arg),*)
405 };
406}
407
408#[macro_export]
410macro_rules! vortex_bail {
411 ($($tt:tt)+) => {
412 return Err($crate::vortex_err!($($tt)+))
413 };
414}
415
416#[macro_export]
419macro_rules! vortex_ensure {
420 ($cond:expr) => {
421 $crate::vortex_ensure!($cond, AssertionFailed: "{}", stringify!($cond));
422 };
423 ($cond:expr, $($tt:tt)*) => {
424 if !$cond {
425 $crate::vortex_bail!($($tt)*);
426 }
427 };
428}
429
430#[macro_export]
466macro_rules! vortex_ensure_eq {
467 ($left:expr, $right:expr $(,)?) => {
468 match (&$left, &$right) {
469 (left_val, right_val) => {
470 if *left_val != *right_val {
471 $crate::vortex_bail!(
472 AssertionFailed: "`{} == {}`\n left: {}\n right: {}",
473 stringify!($left),
474 stringify!($right),
475 left_val,
476 right_val
477 );
478 }
479 }
480 }
481 };
482 ($left:expr, $right:expr, $variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
483 match (&$left, &$right) {
484 (left_val, right_val) => {
485 if *left_val != *right_val {
486 $crate::vortex_bail!(
487 $variant: "{}\n left: {}\n right: {}",
488 format_args!($fmt $(, $arg)*),
489 left_val,
490 right_val
491 );
492 }
493 }
494 }
495 };
496 ($left:expr, $right:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
497 $crate::vortex_ensure_eq!($left, $right, Other: $fmt $(, $arg)*)
498 };
499}
500
501#[macro_export]
504macro_rules! vortex_panic {
505 (OutOfBounds: $idx:expr, $start:expr, $stop:expr) => {{
506 $crate::vortex_panic!($crate::vortex_err!(OutOfBounds: $idx, $start, $stop))
507 }};
508 (NotImplemented: $func:expr, $for_whom:expr) => {{
509 $crate::vortex_panic!($crate::vortex_err!(NotImplemented: $func, $for_whom))
510 }};
511 (MismatchedTypes: $expected:literal, $actual:expr) => {{
512 $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
513 }};
514 (MismatchedTypes: $expected:expr, $actual:expr) => {{
515 $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
516 }};
517 (Context: $msg:literal, $err:expr) => {{
518 $crate::vortex_panic!($crate::vortex_err!(Context: $msg, $err))
519 }};
520 ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
521 $crate::vortex_panic!($crate::vortex_err!($variant: $fmt, $($arg),*))
522 };
523 ($err:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
524 $crate::__private::panic_err_ctx($err, &format_args!($fmt $(, $arg)*))
525 };
526 ($fmt:literal $(, $arg:expr)* $(,)?) => {
527 $crate::__private::panic_fmt(&format_args!($fmt $(, $arg)*))
528 };
529 ($err:expr) => {
530 $crate::__private::panic_err($err)
531 };
532}
533
534impl From<arrow_schema::ArrowError> for VortexError {
535 fn from(value: arrow_schema::ArrowError) -> Self {
536 VortexError::Arrow(value, Box::new(Backtrace::capture()))
537 }
538}
539
540#[cfg(feature = "flatbuffers")]
541impl From<flatbuffers::InvalidFlatbuffer> for VortexError {
542 fn from(value: flatbuffers::InvalidFlatbuffer) -> Self {
543 VortexError::FlatBuffers(value, Box::new(Backtrace::capture()))
544 }
545}
546
547impl From<io::Error> for VortexError {
548 fn from(value: io::Error) -> Self {
549 VortexError::Io(value, Box::new(Backtrace::capture()))
550 }
551}
552
553#[cfg(feature = "object_store")]
554impl From<object_store::Error> for VortexError {
555 fn from(value: object_store::Error) -> Self {
556 VortexError::ObjectStore(value, Box::new(Backtrace::capture()))
557 }
558}
559
560impl From<jiff::Error> for VortexError {
561 fn from(value: jiff::Error) -> Self {
562 VortexError::Jiff(value, Box::new(Backtrace::capture()))
563 }
564}
565
566#[cfg(feature = "tokio")]
567impl From<tokio::task::JoinError> for VortexError {
568 fn from(value: tokio::task::JoinError) -> Self {
569 if value.is_panic() {
570 std::panic::resume_unwind(value.into_panic())
571 } else {
572 VortexError::Join(value, Box::new(Backtrace::capture()))
573 }
574 }
575}
576
577impl From<TryFromIntError> for VortexError {
578 fn from(value: TryFromIntError) -> Self {
579 VortexError::TryFromInt(value, Box::new(Backtrace::capture()))
580 }
581}
582
583impl From<prost::EncodeError> for VortexError {
584 fn from(value: prost::EncodeError) -> Self {
585 Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
586 }
587}
588
589impl From<prost::DecodeError> for VortexError {
590 fn from(value: prost::DecodeError) -> Self {
591 Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
592 }
593}
594
595impl From<prost::UnknownEnumValue> for VortexError {
596 fn from(value: prost::UnknownEnumValue) -> Self {
597 Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
598 }
599}
600
601#[doc(hidden)]
608#[expect(
609 clippy::panic,
610 reason = "cold handlers for vortex_panic!/vortex_expect intentionally panic"
611)]
612pub mod __private {
613 use std::backtrace::Backtrace;
614 use std::error::Error;
615 use std::fmt;
616 use std::fmt::Arguments;
617
618 use crate::ErrString;
619 use crate::VortexError;
620
621 #[doc(hidden)]
622 #[inline]
623 #[cold]
624 #[must_use]
625 pub const fn must_use(error: VortexError) -> VortexError {
626 error
627 }
628
629 #[doc(hidden)]
630 #[cold]
631 #[inline(never)]
632 #[must_use]
633 pub fn fmt_err(
634 variant: fn(ErrString, Box<Backtrace>) -> VortexError,
635 args: &Arguments<'_>,
636 ) -> VortexError {
637 variant(args.to_string().into(), Box::new(Backtrace::capture()))
638 }
639
640 #[doc(hidden)]
641 #[cold]
642 #[inline(never)]
643 #[must_use]
644 pub fn out_of_bounds(idx: usize, start: usize, stop: usize) -> VortexError {
645 VortexError::OutOfBounds(idx, start, stop, Box::new(Backtrace::capture()))
646 }
647
648 #[doc(hidden)]
649 #[cold]
650 #[inline(never)]
651 #[must_use]
652 pub fn not_implemented(func: impl Into<ErrString>, by_whom: &Arguments<'_>) -> VortexError {
653 VortexError::NotImplemented(
654 func.into(),
655 by_whom.to_string().into(),
656 Box::new(Backtrace::capture()),
657 )
658 }
659
660 #[doc(hidden)]
661 #[cold]
662 #[inline(never)]
663 #[must_use]
664 pub fn mismatched_types(expected: &dyn fmt::Display, actual: &dyn fmt::Display) -> VortexError {
665 VortexError::MismatchedTypes(
666 expected.to_string().into(),
667 actual.to_string().into(),
668 Box::new(Backtrace::capture()),
669 )
670 }
671
672 #[doc(hidden)]
673 #[cold]
674 #[inline(never)]
675 #[must_use]
676 pub fn context(msg: &'static str, err: VortexError) -> VortexError {
677 VortexError::Context(msg.into(), Box::new(err))
678 }
679
680 #[doc(hidden)]
681 #[cold]
682 #[inline(never)]
683 #[must_use]
684 pub fn external(err: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> VortexError {
685 VortexError::External(err.into(), Box::new(Backtrace::capture()))
686 }
687
688 #[doc(hidden)]
689 #[cold]
690 #[inline(never)]
691 pub fn panic_err(err: VortexError) -> ! {
692 panic!("{err}")
693 }
694
695 #[doc(hidden)]
696 #[cold]
697 #[inline(never)]
698 pub fn panic_fmt(args: &Arguments<'_>) -> ! {
699 panic!(
700 "{}",
701 VortexError::Other(args.to_string().into(), Box::new(Backtrace::capture()))
702 )
703 }
704
705 #[doc(hidden)]
706 #[cold]
707 #[inline(never)]
708 pub fn panic_err_ctx(err: VortexError, args: &Arguments<'_>) -> ! {
709 panic!("{}", err.with_context(args.to_string()))
710 }
711
712 #[doc(hidden)]
713 #[cold]
714 #[inline(never)]
715 pub fn result_expect_failed(err: impl Into<VortexError>, msg: &'static str) -> ! {
716 panic!("{}", err.into().with_context(msg))
717 }
718
719 #[doc(hidden)]
720 #[cold]
721 #[inline(never)]
722 pub fn option_expect_failed(msg: &'static str) -> ! {
723 panic!(
724 "{}",
725 VortexError::AssertionFailed(msg.into(), Box::new(Backtrace::capture()))
726 )
727 }
728}