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 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]
433macro_rules! vortex_ensure_eq {
434 ($left:expr, $right:expr) => {
435 $crate::vortex_ensure_eq!($left, $right, AssertionFailed: "{} != {}: {:?} != {:?}", stringify!($left), stringify!($right), $left, $right);
436 };
437 ($left:expr, $right:expr, $($tt:tt)*) => {
438 if $left != $right {
439 $crate::vortex_bail!($($tt)*);
440 }
441 };
442}
443
444#[macro_export]
447macro_rules! vortex_panic {
448 (OutOfBounds: $idx:expr, $start:expr, $stop:expr) => {{
449 $crate::vortex_panic!($crate::vortex_err!(OutOfBounds: $idx, $start, $stop))
450 }};
451 (NotImplemented: $func:expr, $for_whom:expr) => {{
452 $crate::vortex_panic!($crate::vortex_err!(NotImplemented: $func, $for_whom))
453 }};
454 (MismatchedTypes: $expected:literal, $actual:expr) => {{
455 $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
456 }};
457 (MismatchedTypes: $expected:expr, $actual:expr) => {{
458 $crate::vortex_panic!($crate::vortex_err!(MismatchedTypes: $expected, $actual))
459 }};
460 (Context: $msg:literal, $err:expr) => {{
461 $crate::vortex_panic!($crate::vortex_err!(Context: $msg, $err))
462 }};
463 ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
464 $crate::vortex_panic!($crate::vortex_err!($variant: $fmt, $($arg),*))
465 };
466 ($err:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
467 $crate::__private::panic_err_ctx($err, &format_args!($fmt $(, $arg)*))
468 };
469 ($fmt:literal $(, $arg:expr)* $(,)?) => {
470 $crate::__private::panic_fmt(&format_args!($fmt $(, $arg)*))
471 };
472 ($err:expr) => {
473 $crate::__private::panic_err($err)
474 };
475}
476
477impl From<arrow_schema::ArrowError> for VortexError {
478 fn from(value: arrow_schema::ArrowError) -> Self {
479 VortexError::Arrow(value, Box::new(Backtrace::capture()))
480 }
481}
482
483#[cfg(feature = "flatbuffers")]
484impl From<flatbuffers::InvalidFlatbuffer> for VortexError {
485 fn from(value: flatbuffers::InvalidFlatbuffer) -> Self {
486 VortexError::FlatBuffers(value, Box::new(Backtrace::capture()))
487 }
488}
489
490impl From<io::Error> for VortexError {
491 fn from(value: io::Error) -> Self {
492 VortexError::Io(value, Box::new(Backtrace::capture()))
493 }
494}
495
496#[cfg(feature = "object_store")]
497impl From<object_store::Error> for VortexError {
498 fn from(value: object_store::Error) -> Self {
499 VortexError::ObjectStore(value, Box::new(Backtrace::capture()))
500 }
501}
502
503impl From<jiff::Error> for VortexError {
504 fn from(value: jiff::Error) -> Self {
505 VortexError::Jiff(value, Box::new(Backtrace::capture()))
506 }
507}
508
509#[cfg(feature = "tokio")]
510impl From<tokio::task::JoinError> for VortexError {
511 fn from(value: tokio::task::JoinError) -> Self {
512 if value.is_panic() {
513 std::panic::resume_unwind(value.into_panic())
514 } else {
515 VortexError::Join(value, Box::new(Backtrace::capture()))
516 }
517 }
518}
519
520impl From<TryFromIntError> for VortexError {
521 fn from(value: TryFromIntError) -> Self {
522 VortexError::TryFromInt(value, Box::new(Backtrace::capture()))
523 }
524}
525
526impl From<prost::EncodeError> for VortexError {
527 fn from(value: prost::EncodeError) -> Self {
528 Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
529 }
530}
531
532impl From<prost::DecodeError> for VortexError {
533 fn from(value: prost::DecodeError) -> Self {
534 Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
535 }
536}
537
538impl From<prost::UnknownEnumValue> for VortexError {
539 fn from(value: prost::UnknownEnumValue) -> Self {
540 Self::Prost(Box::new(value), Box::new(Backtrace::capture()))
541 }
542}
543
544#[doc(hidden)]
551#[expect(
552 clippy::panic,
553 reason = "cold handlers for vortex_panic!/vortex_expect intentionally panic"
554)]
555pub mod __private {
556 use std::backtrace::Backtrace;
557 use std::error::Error;
558 use std::fmt;
559 use std::fmt::Arguments;
560
561 use crate::ErrString;
562 use crate::VortexError;
563
564 #[doc(hidden)]
565 #[inline]
566 #[cold]
567 #[must_use]
568 pub const fn must_use(error: VortexError) -> VortexError {
569 error
570 }
571
572 #[doc(hidden)]
573 #[cold]
574 #[inline(never)]
575 #[must_use]
576 pub fn fmt_err(
577 variant: fn(ErrString, Box<Backtrace>) -> VortexError,
578 args: &Arguments<'_>,
579 ) -> VortexError {
580 variant(args.to_string().into(), Box::new(Backtrace::capture()))
581 }
582
583 #[doc(hidden)]
584 #[cold]
585 #[inline(never)]
586 #[must_use]
587 pub fn out_of_bounds(idx: usize, start: usize, stop: usize) -> VortexError {
588 VortexError::OutOfBounds(idx, start, stop, Box::new(Backtrace::capture()))
589 }
590
591 #[doc(hidden)]
592 #[cold]
593 #[inline(never)]
594 #[must_use]
595 pub fn not_implemented(func: impl Into<ErrString>, by_whom: &Arguments<'_>) -> VortexError {
596 VortexError::NotImplemented(
597 func.into(),
598 by_whom.to_string().into(),
599 Box::new(Backtrace::capture()),
600 )
601 }
602
603 #[doc(hidden)]
604 #[cold]
605 #[inline(never)]
606 #[must_use]
607 pub fn mismatched_types(expected: &dyn fmt::Display, actual: &dyn fmt::Display) -> VortexError {
608 VortexError::MismatchedTypes(
609 expected.to_string().into(),
610 actual.to_string().into(),
611 Box::new(Backtrace::capture()),
612 )
613 }
614
615 #[doc(hidden)]
616 #[cold]
617 #[inline(never)]
618 #[must_use]
619 pub fn context(msg: &'static str, err: VortexError) -> VortexError {
620 VortexError::Context(msg.into(), Box::new(err))
621 }
622
623 #[doc(hidden)]
624 #[cold]
625 #[inline(never)]
626 #[must_use]
627 pub fn external(err: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> VortexError {
628 VortexError::External(err.into(), Box::new(Backtrace::capture()))
629 }
630
631 #[doc(hidden)]
632 #[cold]
633 #[inline(never)]
634 pub fn panic_err(err: VortexError) -> ! {
635 panic!("{err}")
636 }
637
638 #[doc(hidden)]
639 #[cold]
640 #[inline(never)]
641 pub fn panic_fmt(args: &Arguments<'_>) -> ! {
642 panic!(
643 "{}",
644 VortexError::Other(args.to_string().into(), Box::new(Backtrace::capture()))
645 )
646 }
647
648 #[doc(hidden)]
649 #[cold]
650 #[inline(never)]
651 pub fn panic_err_ctx(err: VortexError, args: &Arguments<'_>) -> ! {
652 panic!("{}", err.with_context(args.to_string()))
653 }
654
655 #[doc(hidden)]
656 #[cold]
657 #[inline(never)]
658 pub fn result_expect_failed(err: impl Into<VortexError>, msg: &'static str) -> ! {
659 panic!("{}", err.into().with_context(msg))
660 }
661
662 #[doc(hidden)]
663 #[cold]
664 #[inline(never)]
665 pub fn option_expect_failed(msg: &'static str) -> ! {
666 panic!(
667 "{}",
668 VortexError::AssertionFailed(msg.into(), Box::new(Backtrace::capture()))
669 )
670 }
671}