1use std::fmt;
5
6use arrow_schema::ArrowError;
7use snafu::{IntoError as _, Location, Snafu};
8
9type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
10
11#[cfg(feature = "backtrace")]
12mod backtrace_support {
13 use std::backtrace::Backtrace;
14
15 use snafu::{AsBacktrace, GenerateImplicitData};
16
17 #[derive(Debug)]
18 pub struct MaybeBacktrace(pub Option<Backtrace>);
19
20 impl GenerateImplicitData for MaybeBacktrace {
21 fn generate() -> Self {
22 Self(<Option<Backtrace>>::generate())
23 }
24 }
25
26 impl AsBacktrace for MaybeBacktrace {
27 fn as_backtrace(&self) -> Option<&Backtrace> {
28 self.0.as_ref()
29 }
30 }
31}
32
33#[cfg(not(feature = "backtrace"))]
34mod backtrace_support {
35 use std::backtrace::Backtrace;
36
37 use snafu::{AsBacktrace, GenerateImplicitData};
38
39 #[derive(Debug)]
40 pub struct MaybeBacktrace;
41
42 impl GenerateImplicitData for MaybeBacktrace {
43 fn generate() -> Self {
44 Self
45 }
46 }
47
48 impl AsBacktrace for MaybeBacktrace {
49 fn as_backtrace(&self) -> Option<&Backtrace> {
50 None
51 }
52 }
53}
54
55use backtrace_support::MaybeBacktrace;
56
57#[derive(Debug)]
62pub struct FieldNotFoundError {
63 pub field_name: String,
64 pub candidates: Vec<String>,
65}
66
67impl fmt::Display for FieldNotFoundError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "Field '{}' not found.", self.field_name)?;
70 let suggestion =
71 crate::levenshtein::find_best_suggestion(&self.field_name, &self.candidates);
72 if let Some(suggestion) = suggestion {
73 write!(f, " Did you mean '{}'?", suggestion)?;
74 }
75 write!(f, "\nAvailable fields: [")?;
76 for (i, candidate) in self.candidates.iter().take(10).enumerate() {
77 if i > 0 {
78 write!(f, ", ")?;
79 }
80 write!(f, "'{}'", candidate)?;
81 }
82 if self.candidates.len() > 10 {
83 let remaining = self.candidates.len() - 10;
84 write!(f, ", ... and {} more]", remaining)?;
85 } else {
86 write!(f, "]")?;
87 }
88 Ok(())
89 }
90}
91
92impl std::error::Error for FieldNotFoundError {}
93
94#[derive(Debug)]
100pub struct CommitStatusUnknownError {
101 version: u64,
102 source: BoxedError,
103}
104
105impl CommitStatusUnknownError {
106 pub fn version(&self) -> u64 {
108 self.version
109 }
110}
111
112impl std::fmt::Display for CommitStatusUnknownError {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 write!(
115 f,
116 "Commit result for version {} is unknown: the commit may or may not have been \
117 applied; check the table state before retrying: {}",
118 self.version, self.source
119 )
120 }
121}
122
123impl std::error::Error for CommitStatusUnknownError {
124 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
125 Some(self.source.as_ref())
126 }
127}
128
129#[inline]
131pub fn box_error(e: impl std::error::Error + Send + Sync + 'static) -> BoxedError {
132 Box::new(e)
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
138pub enum FenceReason {
139 PeerClaimedEpoch,
141 PersistenceFailure,
144}
145
146impl std::fmt::Display for FenceReason {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 let s = match self {
150 Self::PeerClaimedEpoch => "peer claimed epoch",
151 Self::PersistenceFailure => "persistence failure",
152 };
153 f.write_str(s)
154 }
155}
156
157#[derive(Debug, Snafu)]
158#[snafu(visibility(pub))]
159pub enum Error {
160 #[snafu(display("Invalid user input: {source}, {location}"))]
161 InvalidInput {
162 source: BoxedError,
163 #[snafu(implicit)]
164 location: Location,
165 #[snafu(implicit)]
166 backtrace: MaybeBacktrace,
167 },
168 #[snafu(display("Dataset already exists: {uri}, {location}"))]
169 DatasetAlreadyExists {
170 uri: String,
171 #[snafu(implicit)]
172 location: Location,
173 #[snafu(implicit)]
174 backtrace: MaybeBacktrace,
175 },
176 #[snafu(display("Append with different schema: {difference}, location: {location}"))]
177 SchemaMismatch {
178 difference: String,
179 #[snafu(implicit)]
180 location: Location,
181 #[snafu(implicit)]
182 backtrace: MaybeBacktrace,
183 },
184 #[snafu(display("Dataset at path {path} was not found: {source}, {location}"))]
185 DatasetNotFound {
186 path: String,
187 source: BoxedError,
188 #[snafu(implicit)]
189 location: Location,
190 #[snafu(implicit)]
191 backtrace: MaybeBacktrace,
192 },
193 #[snafu(display("Encountered corrupt file {path}: {source}, {location}"))]
194 CorruptFile {
195 path: object_store::path::Path,
196 source: BoxedError,
197 #[snafu(implicit)]
198 location: Location,
199 #[snafu(implicit)]
200 backtrace: MaybeBacktrace,
201 },
202 #[snafu(display("Not supported: {source}, {location}"))]
203 NotSupported {
204 source: BoxedError,
205 #[snafu(implicit)]
206 location: Location,
207 #[snafu(implicit)]
208 backtrace: MaybeBacktrace,
209 },
210 #[snafu(display("Commit conflict for version {version}: {source}, {location}"))]
211 CommitConflict {
212 version: u64,
213 source: BoxedError,
214 #[snafu(implicit)]
215 location: Location,
216 #[snafu(implicit)]
217 backtrace: MaybeBacktrace,
218 },
219 #[snafu(display("Incompatible transaction: {source}, {location}"))]
220 IncompatibleTransaction {
221 source: BoxedError,
222 #[snafu(implicit)]
223 location: Location,
224 #[snafu(implicit)]
225 backtrace: MaybeBacktrace,
226 },
227 #[snafu(display("Retryable commit conflict for version {version}: {source}, {location}"))]
228 RetryableCommitConflict {
229 version: u64,
230 source: BoxedError,
231 #[snafu(implicit)]
232 location: Location,
233 #[snafu(implicit)]
234 backtrace: MaybeBacktrace,
235 },
236 #[snafu(display("Too many concurrent writers. {message}, {location}"))]
237 TooMuchWriteContention {
238 message: String,
239 #[snafu(implicit)]
240 location: Location,
241 #[snafu(implicit)]
242 backtrace: MaybeBacktrace,
243 },
244 #[snafu(display("Operation timed out: {message}, {location}"))]
245 Timeout {
246 message: String,
247 #[snafu(implicit)]
248 location: Location,
249 },
250 #[snafu(display(
251 "Encountered internal error. Please file a bug report at https://github.com/lance-format/lance/issues. {message}, {location}"
252 ))]
253 Internal {
254 message: String,
255 #[snafu(implicit)]
256 location: Location,
257 #[snafu(implicit)]
258 backtrace: MaybeBacktrace,
259 },
260 #[snafu(display("A prerequisite task failed: {message}, {location}"))]
261 PrerequisiteFailed {
262 message: String,
263 #[snafu(implicit)]
264 location: Location,
265 #[snafu(implicit)]
266 backtrace: MaybeBacktrace,
267 },
268 #[snafu(display("Unprocessable: {message}, {location}"))]
269 Unprocessable {
270 message: String,
271 #[snafu(implicit)]
272 location: Location,
273 #[snafu(implicit)]
274 backtrace: MaybeBacktrace,
275 },
276 #[snafu(display("LanceError(Arrow): {message}, {location}"))]
277 Arrow {
278 message: String,
279 #[snafu(implicit)]
280 location: Location,
281 #[snafu(implicit)]
282 backtrace: MaybeBacktrace,
283 },
284 #[snafu(display("LanceError(Schema): {message}, {location}"))]
285 Schema {
286 message: String,
287 #[snafu(implicit)]
288 location: Location,
289 #[snafu(implicit)]
290 backtrace: MaybeBacktrace,
291 },
292 #[snafu(display("Not found: {uri}, {location}"))]
293 NotFound {
294 uri: String,
295 #[snafu(implicit)]
296 location: Location,
297 #[snafu(implicit)]
298 backtrace: MaybeBacktrace,
299 },
300 #[snafu(display("LanceError(IO): {source}, {location}"))]
301 IO {
302 source: BoxedError,
303 #[snafu(implicit)]
304 location: Location,
305 #[snafu(implicit)]
306 backtrace: MaybeBacktrace,
307 },
308 #[snafu(display("LanceError(Index): {message}, {location}"))]
309 Index {
310 message: String,
311 #[snafu(implicit)]
312 location: Location,
313 #[snafu(implicit)]
314 backtrace: MaybeBacktrace,
315 },
316 #[snafu(display("Lance index not found: {identity}, {location}"))]
317 IndexNotFound {
318 identity: String,
319 #[snafu(implicit)]
320 location: Location,
321 #[snafu(implicit)]
322 backtrace: MaybeBacktrace,
323 },
324 #[snafu(display("Cannot infer storage location from: {message}"))]
325 InvalidTableLocation { message: String },
326 Stop,
328 #[snafu(display("Wrapped error: {error}, {location}"))]
329 Wrapped {
330 #[snafu(source)]
331 error: BoxedError,
332 #[snafu(implicit)]
333 location: Location,
334 #[snafu(implicit)]
335 backtrace: MaybeBacktrace,
336 },
337 #[snafu(display("Cloned error: {message}, {location}"))]
338 Cloned {
339 message: String,
340 #[snafu(implicit)]
341 location: Location,
342 #[snafu(implicit)]
343 backtrace: MaybeBacktrace,
344 },
345 #[snafu(display("Query Execution error: {message}, {location}"))]
346 Execution {
347 message: String,
348 #[snafu(implicit)]
349 location: Location,
350 #[snafu(implicit)]
351 backtrace: MaybeBacktrace,
352 },
353 #[snafu(display("Ref is invalid: {message}"))]
354 InvalidRef { message: String },
355 #[snafu(display("Ref conflict error: {message}"))]
356 RefConflict { message: String },
357 #[snafu(display("Ref not found error: {message}"))]
358 RefNotFound { message: String },
359 #[snafu(display("Cleanup error: {message}"))]
360 Cleanup { message: String },
361 #[snafu(display("Version not found error: {message}"))]
362 VersionNotFound { message: String },
363 #[snafu(display("Version conflict error: {message}"))]
364 VersionConflict {
365 message: String,
366 major_version: u16,
367 minor_version: u16,
368 #[snafu(implicit)]
369 location: Location,
370 #[snafu(implicit)]
371 backtrace: MaybeBacktrace,
372 },
373 #[snafu(display("Namespace error: {source}, {location}"))]
374 Namespace {
375 source: BoxedError,
376 #[snafu(implicit)]
377 location: Location,
378 #[snafu(implicit)]
379 backtrace: MaybeBacktrace,
380 },
381 #[snafu(transparent)]
387 External { source: BoxedError },
388
389 #[snafu(transparent)]
391 FieldNotFound { source: FieldNotFoundError },
392
393 #[snafu(display(
394 "Spill disk cap of {cap_bytes} bytes exceeded; currently using {used_bytes} bytes, {location}"
395 ))]
396 DiskCapExceeded {
397 cap_bytes: u64,
398 used_bytes: u64,
399 #[snafu(implicit)]
400 location: Location,
401 },
402 #[snafu(display("Writer fenced ({reason}): {message}, {location}"))]
406 Fenced {
407 reason: FenceReason,
408 message: String,
409 #[snafu(implicit)]
410 location: Location,
411 },
412 #[snafu(display("Write rejected by backpressure: {message}, {location}"))]
420 Backpressure {
421 message: String,
422 #[snafu(implicit)]
423 location: Location,
424 },
425}
426
427impl Error {
428 #[cfg(feature = "backtrace")]
433 pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
434 match self {
435 Self::InvalidInput { backtrace, .. }
436 | Self::DatasetAlreadyExists { backtrace, .. }
437 | Self::SchemaMismatch { backtrace, .. }
438 | Self::DatasetNotFound { backtrace, .. }
439 | Self::CorruptFile { backtrace, .. }
440 | Self::NotSupported { backtrace, .. }
441 | Self::CommitConflict { backtrace, .. }
442 | Self::IncompatibleTransaction { backtrace, .. }
443 | Self::RetryableCommitConflict { backtrace, .. }
444 | Self::TooMuchWriteContention { backtrace, .. }
445 | Self::Internal { backtrace, .. }
446 | Self::PrerequisiteFailed { backtrace, .. }
447 | Self::Unprocessable { backtrace, .. }
448 | Self::Arrow { backtrace, .. }
449 | Self::Schema { backtrace, .. }
450 | Self::NotFound { backtrace, .. }
451 | Self::IO { backtrace, .. }
452 | Self::Index { backtrace, .. }
453 | Self::IndexNotFound { backtrace, .. }
454 | Self::Wrapped { backtrace, .. }
455 | Self::Cloned { backtrace, .. }
456 | Self::Execution { backtrace, .. }
457 | Self::VersionConflict { backtrace, .. }
458 | Self::Namespace { backtrace, .. } => {
459 use snafu::AsBacktrace;
460 backtrace.as_backtrace()
461 }
462 Self::InvalidTableLocation { .. }
465 | Self::Stop
466 | Self::InvalidRef { .. }
467 | Self::RefConflict { .. }
468 | Self::RefNotFound { .. }
469 | Self::Cleanup { .. }
470 | Self::VersionNotFound { .. }
471 | Self::External { .. }
472 | Self::FieldNotFound { .. }
473 | Self::Timeout { .. }
474 | Self::DiskCapExceeded { .. }
475 | Self::Fenced { .. }
476 | Self::Backpressure { .. } => None,
477 }
478 }
479
480 #[cfg(not(feature = "backtrace"))]
484 pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
485 None
486 }
487
488 #[track_caller]
489 pub fn corrupt_file(path: object_store::path::Path, message: impl Into<String>) -> Self {
490 CorruptFileSnafu { path }.into_error(message.into().into())
491 }
492
493 #[track_caller]
501 pub fn corrupt_file_named(name: &str, message: impl Into<String>) -> Self {
502 Self::corrupt_file(object_store::path::Path::from(name), message)
503 }
504
505 #[track_caller]
506 pub fn invalid_input(message: impl Into<String>) -> Self {
507 InvalidInputSnafu.into_error(message.into().into())
508 }
509
510 #[track_caller]
511 pub fn invalid_input_source(source: BoxedError) -> Self {
512 InvalidInputSnafu.into_error(source)
513 }
514
515 #[track_caller]
516 pub fn io(message: impl Into<String>) -> Self {
517 IOSnafu.into_error(message.into().into())
518 }
519
520 #[track_caller]
522 pub fn fenced_by_peer(message: impl Into<String>) -> Self {
523 FencedSnafu {
524 reason: FenceReason::PeerClaimedEpoch,
525 message: message.into(),
526 }
527 .build()
528 }
529
530 #[track_caller]
533 pub fn writer_poisoned(message: impl Into<String>) -> Self {
534 FencedSnafu {
535 reason: FenceReason::PersistenceFailure,
536 message: message.into(),
537 }
538 .build()
539 }
540
541 pub fn fence_reason(&self) -> Option<FenceReason> {
544 match self {
545 Self::Fenced { reason, .. } => Some(*reason),
546 _ => None,
547 }
548 }
549
550 #[track_caller]
553 pub fn backpressure(message: impl Into<String>) -> Self {
554 BackpressureSnafu {
555 message: message.into(),
556 }
557 .build()
558 }
559
560 pub fn is_backpressure(&self) -> bool {
564 matches!(self, Self::Backpressure { .. })
565 }
566
567 #[track_caller]
568 pub fn io_source(source: BoxedError) -> Self {
569 IOSnafu.into_error(source)
570 }
571
572 #[track_caller]
573 pub fn dataset_already_exists(uri: impl Into<String>) -> Self {
574 DatasetAlreadyExistsSnafu { uri: uri.into() }.build()
575 }
576
577 #[track_caller]
578 pub fn dataset_not_found(path: impl Into<String>, source: BoxedError) -> Self {
579 DatasetNotFoundSnafu { path: path.into() }.into_error(source)
580 }
581
582 #[track_caller]
583 pub fn version_conflict(
584 message: impl Into<String>,
585 major_version: u16,
586 minor_version: u16,
587 ) -> Self {
588 VersionConflictSnafu {
589 message: message.into(),
590 major_version,
591 minor_version,
592 }
593 .build()
594 }
595
596 #[track_caller]
597 pub fn not_found(uri: impl Into<String>) -> Self {
598 NotFoundSnafu { uri: uri.into() }.build()
599 }
600
601 pub fn is_not_found(&self) -> bool {
603 match self {
604 Self::NotFound { .. } => true,
605 Self::Wrapped { error, .. }
606 if error.downcast_ref::<CommitStatusUnknownError>().is_some() =>
607 {
608 false
609 }
610 Self::IO { source, .. } | Self::Wrapped { error: source, .. } => {
611 error_source_is_not_found(source.as_ref())
612 }
613 _ => false,
614 }
615 }
616
617 #[track_caller]
618 pub fn wrapped(error: BoxedError) -> Self {
619 WrappedSnafu.into_error(error)
620 }
621
622 #[track_caller]
623 pub fn schema(message: impl Into<String>) -> Self {
624 SchemaSnafu {
625 message: message.into(),
626 }
627 .build()
628 }
629
630 #[track_caller]
631 pub fn not_supported(message: impl Into<String>) -> Self {
632 NotSupportedSnafu.into_error(message.into().into())
633 }
634
635 #[track_caller]
636 pub fn not_supported_source(source: BoxedError) -> Self {
637 NotSupportedSnafu.into_error(source)
638 }
639
640 #[track_caller]
641 pub fn internal(message: impl Into<String>) -> Self {
642 InternalSnafu {
643 message: message.into(),
644 }
645 .build()
646 }
647
648 #[track_caller]
649 pub fn timeout(message: impl Into<String>) -> Self {
650 TimeoutSnafu {
651 message: message.into(),
652 }
653 .build()
654 }
655
656 #[track_caller]
657 pub fn namespace(message: impl Into<String>) -> Self {
658 NamespaceSnafu.into_error(message.into().into())
659 }
660
661 #[track_caller]
662 pub fn namespace_source(source: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
663 NamespaceSnafu.into_error(source)
664 }
665
666 #[track_caller]
667 pub fn arrow(message: impl Into<String>) -> Self {
668 ArrowSnafu {
669 message: message.into(),
670 }
671 .build()
672 }
673
674 #[track_caller]
675 pub fn execution(message: impl Into<String>) -> Self {
676 ExecutionSnafu {
677 message: message.into(),
678 }
679 .build()
680 }
681
682 #[track_caller]
683 pub fn cloned(message: impl Into<String>) -> Self {
684 ClonedSnafu {
685 message: message.into(),
686 }
687 .build()
688 }
689
690 #[track_caller]
691 pub fn schema_mismatch(difference: impl Into<String>) -> Self {
692 SchemaMismatchSnafu {
693 difference: difference.into(),
694 }
695 .build()
696 }
697
698 #[track_caller]
699 pub fn unprocessable(message: impl Into<String>) -> Self {
700 UnprocessableSnafu {
701 message: message.into(),
702 }
703 .build()
704 }
705
706 #[track_caller]
707 pub fn too_much_write_contention(message: impl Into<String>) -> Self {
708 TooMuchWriteContentionSnafu {
709 message: message.into(),
710 }
711 .build()
712 }
713
714 #[track_caller]
715 pub fn prerequisite_failed(message: impl Into<String>) -> Self {
716 PrerequisiteFailedSnafu {
717 message: message.into(),
718 }
719 .build()
720 }
721
722 #[track_caller]
723 pub fn index(message: impl Into<String>) -> Self {
724 IndexSnafu {
725 message: message.into(),
726 }
727 .build()
728 }
729
730 #[track_caller]
731 pub fn index_not_found(identity: impl Into<String>) -> Self {
732 IndexNotFoundSnafu {
733 identity: identity.into(),
734 }
735 .build()
736 }
737
738 #[track_caller]
739 pub fn commit_conflict_source(version: u64, source: BoxedError) -> Self {
740 CommitConflictSnafu { version }.into_error(source)
741 }
742
743 #[track_caller]
744 pub fn retryable_commit_conflict_source(version: u64, source: BoxedError) -> Self {
745 RetryableCommitConflictSnafu { version }.into_error(source)
746 }
747
748 #[track_caller]
749 pub fn commit_status_unknown_source(version: u64, source: BoxedError) -> Self {
750 Self::wrapped(box_error(CommitStatusUnknownError { version, source }))
751 }
752
753 pub fn is_commit_status_unknown(&self) -> bool {
756 matches!(
757 self,
758 Self::Wrapped { error, .. }
759 if error.downcast_ref::<CommitStatusUnknownError>().is_some()
760 )
761 }
762
763 #[track_caller]
764 pub fn incompatible_transaction_source(source: BoxedError) -> Self {
765 IncompatibleTransactionSnafu.into_error(source)
766 }
767
768 #[track_caller]
769 pub fn disk_cap_exceeded(cap_bytes: u64, used_bytes: u64) -> Self {
770 DiskCapExceededSnafu {
771 cap_bytes,
772 used_bytes,
773 }
774 .build()
775 }
776
777 pub fn external(source: BoxedError) -> Self {
779 Self::External { source }
780 }
781
782 pub fn field_not_found(field_name: impl Into<String>, candidates: Vec<String>) -> Self {
784 Self::FieldNotFound {
785 source: FieldNotFoundError {
786 field_name: field_name.into(),
787 candidates,
788 },
789 }
790 }
791
792 pub fn external_source(&self) -> Option<&BoxedError> {
796 match self {
797 Self::External { source } => Some(source),
798 _ => None,
799 }
800 }
801
802 pub fn into_external(self) -> std::result::Result<BoxedError, Self> {
806 match self {
807 Self::External { source } => Ok(source),
808 other => Err(other),
809 }
810 }
811}
812
813fn error_source_is_not_found(source: &(dyn std::error::Error + 'static)) -> bool {
814 if let Some(error) = source.downcast_ref::<Error>() {
815 return error.is_not_found();
816 }
817 if let Some(error) = source.downcast_ref::<object_store::Error>() {
818 return matches!(error, object_store::Error::NotFound { .. })
819 || std::error::Error::source(error).is_some_and(error_source_is_not_found);
820 }
821 source.source().is_some_and(error_source_is_not_found)
822}
823
824pub trait LanceOptionExt<T> {
825 fn expect_ok(self) -> Result<T>;
829}
830
831impl<T> LanceOptionExt<T> for Option<T> {
832 #[track_caller]
833 fn expect_ok(self) -> Result<T> {
834 self.ok_or_else(|| Error::internal("Expected option to have value"))
835 }
836}
837
838pub type Result<T> = std::result::Result<T, Error>;
839pub type ArrowResult<T> = std::result::Result<T, ArrowError>;
840#[cfg(feature = "datafusion")]
841pub type DataFusionResult<T> = std::result::Result<T, datafusion_common::DataFusionError>;
842
843impl From<ArrowError> for Error {
844 #[track_caller]
845 fn from(e: ArrowError) -> Self {
846 match e {
847 ArrowError::ExternalError(source) => {
848 match source.downcast::<Self>() {
850 Ok(lance_err) => *lance_err,
851 Err(source) => Self::External { source },
852 }
853 }
854 other => Self::arrow(other.to_string()),
855 }
856 }
857}
858
859impl From<&ArrowError> for Error {
860 #[track_caller]
861 fn from(e: &ArrowError) -> Self {
862 Self::arrow(e.to_string())
863 }
864}
865
866impl From<std::io::Error> for Error {
867 #[track_caller]
868 fn from(e: std::io::Error) -> Self {
869 if e.get_ref().is_some_and(|inner| inner.is::<Self>()) {
874 return *e
875 .into_inner()
876 .expect("checked Some above")
877 .downcast::<Self>()
878 .expect("checked type above");
879 }
880 Self::io_source(box_error(e))
881 }
882}
883
884impl From<object_store::Error> for Error {
885 #[track_caller]
886 fn from(e: object_store::Error) -> Self {
887 match e {
888 object_store::Error::NotFound { path, .. } => Self::not_found(path),
890 other => Self::io_source(box_error(other)),
891 }
892 }
893}
894
895impl From<prost::DecodeError> for Error {
896 #[track_caller]
897 fn from(e: prost::DecodeError) -> Self {
898 Self::io_source(box_error(e))
899 }
900}
901
902impl From<prost::EncodeError> for Error {
903 #[track_caller]
904 fn from(e: prost::EncodeError) -> Self {
905 Self::io_source(box_error(e))
906 }
907}
908
909impl From<prost::UnknownEnumValue> for Error {
910 #[track_caller]
911 fn from(e: prost::UnknownEnumValue) -> Self {
912 Self::io_source(box_error(e))
913 }
914}
915
916impl From<tokio::task::JoinError> for Error {
917 #[track_caller]
918 fn from(e: tokio::task::JoinError) -> Self {
919 Self::io_source(box_error(e))
920 }
921}
922
923impl From<object_store::path::Error> for Error {
924 #[track_caller]
925 fn from(e: object_store::path::Error) -> Self {
926 Self::io_source(box_error(e))
927 }
928}
929
930impl From<url::ParseError> for Error {
931 #[track_caller]
932 fn from(e: url::ParseError) -> Self {
933 Self::io_source(box_error(e))
934 }
935}
936
937impl From<serde_json::Error> for Error {
938 #[track_caller]
939 fn from(e: serde_json::Error) -> Self {
940 Self::arrow(e.to_string())
941 }
942}
943
944impl From<Error> for ArrowError {
945 fn from(value: Error) -> Self {
946 match value {
947 Error::External { source } => Self::ExternalError(source),
949 Error::Schema { message, .. } => Self::SchemaError(message),
951 e => Self::ExternalError(Box::new(e)),
953 }
954 }
955}
956
957#[cfg(feature = "datafusion")]
958impl From<datafusion_sql::sqlparser::parser::ParserError> for Error {
959 #[track_caller]
960 fn from(e: datafusion_sql::sqlparser::parser::ParserError) -> Self {
961 Self::io_source(box_error(e))
962 }
963}
964
965#[cfg(feature = "datafusion")]
966impl From<datafusion_sql::sqlparser::tokenizer::TokenizerError> for Error {
967 #[track_caller]
968 fn from(e: datafusion_sql::sqlparser::tokenizer::TokenizerError) -> Self {
969 Self::io_source(box_error(e))
970 }
971}
972
973#[cfg(feature = "datafusion")]
974impl From<Error> for datafusion_common::DataFusionError {
975 #[track_caller]
976 fn from(e: Error) -> Self {
977 Self::External(Box::new(e))
978 }
979}
980
981#[cfg(feature = "datafusion")]
982impl From<datafusion_common::DataFusionError> for Error {
983 #[track_caller]
984 fn from(e: datafusion_common::DataFusionError) -> Self {
985 match e {
986 datafusion_common::DataFusionError::Diagnostic(_, inner)
993 | datafusion_common::DataFusionError::Context(_, inner) => Self::from(*inner),
994 datafusion_common::DataFusionError::Collection(errors) => {
995 match errors.into_iter().next() {
996 Some(first) => Self::from(first),
999 None => Self::execution("DataFusion returned an empty error collection"),
1000 }
1001 }
1002 datafusion_common::DataFusionError::SQL(..)
1003 | datafusion_common::DataFusionError::Plan(..)
1004 | datafusion_common::DataFusionError::Configuration(..)
1005 | datafusion_common::DataFusionError::SchemaError(..) => {
1006 Self::invalid_input_source(box_error(e))
1007 }
1008 datafusion_common::DataFusionError::ArrowError(arrow_err, _) => Self::from(*arrow_err),
1009 datafusion_common::DataFusionError::NotImplemented(..) => {
1010 Self::not_supported_source(box_error(e))
1011 }
1012 datafusion_common::DataFusionError::Execution(..) => Self::execution(e.to_string()),
1013 datafusion_common::DataFusionError::Shared(shared) => {
1014 match std::sync::Arc::try_unwrap(shared) {
1020 Ok(inner) => Self::from(inner),
1021 Err(shared) => {
1022 let rewrapped = datafusion_common::DataFusionError::Shared(shared);
1023 Self::External {
1024 source: box_error(rewrapped),
1025 }
1026 }
1027 }
1028 }
1029 datafusion_common::DataFusionError::External(source) => {
1030 match source.downcast::<Self>() {
1032 Ok(lance_err) => *lance_err,
1033 Err(source) => Self::External { source },
1034 }
1035 }
1036 _ => Self::io_source(box_error(e)),
1037 }
1038 }
1039}
1040
1041impl From<Error> for object_store::Error {
1045 fn from(err: Error) -> Self {
1046 Self::Generic {
1047 store: "N/A",
1048 source: Box::new(err),
1049 }
1050 }
1051}
1052
1053#[track_caller]
1054pub fn get_caller_location() -> &'static std::panic::Location<'static> {
1055 std::panic::Location::caller()
1056}
1057
1058pub struct CloneableError(pub Error);
1075
1076struct DisplayError(Error);
1077
1078impl fmt::Debug for DisplayError {
1079 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1080 fmt::Display::fmt(self, f)
1081 }
1082}
1083
1084impl fmt::Display for DisplayError {
1085 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1086 fmt::Display::fmt(&self.0, f)
1087 }
1088}
1089
1090impl std::error::Error for DisplayError {
1091 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1092 Some(&self.0)
1093 }
1094}
1095
1096impl Clone for CloneableError {
1097 #[track_caller]
1098 fn clone(&self) -> Self {
1099 if let Error::Wrapped { error, .. } = &self.0
1103 && let Some(unknown) = error.downcast_ref::<CommitStatusUnknownError>()
1104 {
1105 let source = std::error::Error::source(unknown)
1106 .map(|source| source.to_string())
1107 .unwrap_or_else(|| unknown.to_string());
1108 return Self(Error::commit_status_unknown_source(
1109 unknown.version(),
1110 box_error(DisplayError(Error::cloned(source))),
1111 ));
1112 }
1113 match &self.0 {
1114 Error::NotFound { uri, .. } => Self(Error::wrapped(Box::new(DisplayError(
1115 Error::not_found(uri.clone()),
1116 )))),
1117 error if error.is_not_found() => Self(Error::wrapped(Box::new(DisplayError(
1118 Error::not_found(error.to_string()),
1119 )))),
1120 Error::Timeout { message, .. } => Self(Error::timeout(message.clone())),
1121 Error::IO { source, .. } => Self(Error::io(source.to_string())),
1122 Error::Fenced {
1126 reason, message, ..
1127 } => Self(
1128 FencedSnafu {
1129 reason: *reason,
1130 message: message.clone(),
1131 }
1132 .build(),
1133 ),
1134 Error::Backpressure { message, .. } => Self(Error::backpressure(message.clone())),
1135 error => Self(Error::cloned(error.to_string())),
1136 }
1137 }
1138}
1139
1140#[derive(Clone)]
1141pub struct CloneableResult<T: Clone>(pub std::result::Result<T, CloneableError>);
1142
1143impl<T: Clone> From<Result<T>> for CloneableResult<T> {
1144 fn from(result: Result<T>) -> Self {
1145 Self(result.map_err(CloneableError))
1146 }
1147}
1148
1149#[cfg(test)]
1150mod test {
1151 use super::*;
1152 use std::error::Error as _;
1153 use std::fmt;
1154
1155 #[test]
1158 fn cloneable_error_preserves_accessor_contracts() {
1159 for (original, expected) in [
1161 (
1162 Error::fenced_by_peer("peer claimed epoch 7"),
1163 FenceReason::PeerClaimedEpoch,
1164 ),
1165 (
1166 Error::writer_poisoned("wal flush failed"),
1167 FenceReason::PersistenceFailure,
1168 ),
1169 ] {
1170 let original = CloneableError(original);
1171 assert_eq!(original.0.fence_reason(), Some(expected));
1172 assert_eq!(original.clone().0.fence_reason(), Some(expected));
1173 }
1174
1175 let original = CloneableError(Error::backpressure("writer at memory ceiling"));
1176 let cloned = original.clone();
1177 assert!(original.0.is_backpressure());
1178 assert!(cloned.0.is_backpressure());
1179
1180 let original = CloneableError(Error::commit_status_unknown_source(
1181 7,
1182 box_error(DisplayError(Error::timeout("put timed out"))),
1183 ));
1184 let cloned = original.clone();
1185 let twice = cloned.clone();
1187 assert!(original.0.is_commit_status_unknown());
1188 assert!(cloned.0.is_commit_status_unknown());
1189 assert!(!cloned.0.is_not_found());
1190 assert!(twice.0.is_commit_status_unknown());
1191 }
1192
1193 #[test]
1194 fn cloneable_error_preserves_not_found_contract() {
1195 let original = CloneableError(Error::not_found("metadata.lance"));
1196 let cloned = original.clone();
1197 let cloned_again = cloned.clone();
1198 assert!(matches!(original.0, Error::NotFound { .. }));
1199 assert!(cloned.0.is_not_found());
1200 assert!(cloned_again.0.is_not_found());
1201 assert!(cloned.0.to_string().to_lowercase().contains("not found"));
1202 assert!(
1203 cloned_again
1204 .0
1205 .to_string()
1206 .to_lowercase()
1207 .contains("not found")
1208 );
1209 assert!(
1210 format!("{:?}", cloned.0)
1211 .to_lowercase()
1212 .contains("not found")
1213 );
1214 assert!(cloned.0.source().is_some_and(|source| source.is::<Error>()
1215 || source.source().is_some_and(|source| source.is::<Error>())));
1216 let downstream_error = Error::wrapped(Box::new(Error::io_source(Box::new(
1217 object_store::Error::Generic {
1218 store: "N/A",
1219 source: Box::new(cloned.0),
1220 },
1221 ))));
1222 assert!(downstream_error.is_not_found());
1223 assert!(
1224 format!("{downstream_error:?}")
1225 .to_lowercase()
1226 .contains("not found")
1227 );
1228
1229 let original = CloneableError(Error::timeout("metadata read timed out"));
1230 let cloned = original.clone();
1231 assert!(matches!(original.0, Error::Timeout { .. }));
1232 assert!(matches!(cloned.0, Error::Timeout { .. }));
1233
1234 let original = CloneableError(Error::io("metadata read was denied"));
1235 let cloned = original.clone();
1236 assert!(matches!(original.0, Error::IO { .. }));
1237 assert!(matches!(cloned.0, Error::IO { .. }));
1238 }
1239
1240 #[test]
1241 fn test_caller_location_capture() {
1242 let current_fn = get_caller_location();
1243 let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
1246 Err(object_store::Error::Generic {
1247 store: "",
1248 source: "".into(),
1249 })?;
1250 Ok(())
1251 });
1252 match f().unwrap_err() {
1253 Error::IO { location, .. } => {
1254 assert_eq!(location.line(), current_fn.line() + 4, "{}", location)
1256 }
1257 #[allow(unreachable_patterns)]
1258 _ => panic!("expected ObjectStore error"),
1259 }
1260 }
1261
1262 #[test]
1263 fn test_caller_location_capture_not_found() {
1264 let current_fn = get_caller_location();
1265 let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
1266 Err(object_store::Error::NotFound {
1267 path: "some/path".to_string(),
1268 source: "not found".into(),
1269 })?;
1270 Ok(())
1271 });
1272 match f().unwrap_err() {
1273 Error::NotFound { location, .. } => {
1274 assert_eq!(location.line(), current_fn.line() + 2, "{}", location)
1276 }
1277 #[allow(unreachable_patterns)]
1278 other => panic!("expected NotFound, got {:?}", other),
1279 }
1280 }
1281
1282 #[test]
1283 fn test_object_store_not_found_converts_to_not_found() {
1284 let os_err = object_store::Error::NotFound {
1285 path: "test/path".to_string(),
1286 source: "no such file".into(),
1287 };
1288 let lance_err: Error = os_err.into();
1289 match lance_err {
1290 Error::NotFound { uri, .. } => {
1291 assert_eq!(uri, "test/path");
1292 }
1293 other => panic!("Expected NotFound, got {:?}", other),
1294 }
1295 }
1296
1297 #[derive(Debug)]
1298 struct MyCustomError {
1299 code: i32,
1300 message: String,
1301 }
1302
1303 impl fmt::Display for MyCustomError {
1304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1305 write!(f, "MyCustomError({}): {}", self.code, self.message)
1306 }
1307 }
1308
1309 impl std::error::Error for MyCustomError {}
1310
1311 #[test]
1312 fn test_io_error_recovers_wrapped_lance_error() {
1313 let io_err = std::io::Error::other(Error::disk_cap_exceeded(100, 50));
1316 let recovered: Error = io_err.into();
1317 match recovered {
1318 Error::DiskCapExceeded {
1319 cap_bytes,
1320 used_bytes,
1321 ..
1322 } => {
1323 assert_eq!(cap_bytes, 100);
1324 assert_eq!(used_bytes, 50);
1325 }
1326 other => panic!("expected DiskCapExceeded, got {other:?}"),
1327 }
1328 }
1329
1330 #[test]
1331 fn test_io_error_without_lance_error_stays_io() {
1332 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
1334 let converted: Error = io_err.into();
1335 assert!(matches!(converted, Error::IO { .. }));
1336 }
1337
1338 #[test]
1339 fn test_commit_status_unknown_is_structured_without_masking_as_not_found() {
1340 let error = Error::commit_status_unknown_source(
1341 42,
1342 box_error(Error::not_found("temporarily invisible manifest")),
1343 );
1344
1345 assert!(error.is_commit_status_unknown());
1346 assert!(!error.is_not_found());
1347 assert!(error.to_string().contains("version 42 is unknown"));
1348 let Error::Wrapped { error, .. } = error else {
1349 panic!("commit-status-unknown must use the semver-compatible wrapper")
1350 };
1351 let status = error
1352 .downcast_ref::<CommitStatusUnknownError>()
1353 .expect("wrapper must retain the typed commit status");
1354 assert_eq!(status.version(), 42);
1355 }
1356
1357 #[test]
1358 fn test_external_error_creation() {
1359 let custom_err = MyCustomError {
1360 code: 42,
1361 message: "test error".to_string(),
1362 };
1363 let err = Error::external(Box::new(custom_err));
1364
1365 match &err {
1366 Error::External { source } => {
1367 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1368 assert_eq!(recovered.code, 42);
1369 assert_eq!(recovered.message, "test error");
1370 }
1371 _ => panic!("Expected External variant"),
1372 }
1373 }
1374
1375 #[test]
1376 fn test_external_source_method() {
1377 let custom_err = MyCustomError {
1378 code: 123,
1379 message: "source test".to_string(),
1380 };
1381 let err = Error::external(Box::new(custom_err));
1382
1383 let source = err.external_source().expect("should have external source");
1384 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1385 assert_eq!(recovered.code, 123);
1386
1387 let io_err = Error::io("test");
1389 assert!(io_err.external_source().is_none());
1390 }
1391
1392 #[test]
1393 fn test_into_external_method() {
1394 let custom_err = MyCustomError {
1395 code: 456,
1396 message: "into test".to_string(),
1397 };
1398 let err = Error::external(Box::new(custom_err));
1399
1400 match err.into_external() {
1401 Ok(source) => {
1402 let recovered = source.downcast::<MyCustomError>().unwrap();
1403 assert_eq!(recovered.code, 456);
1404 }
1405 Err(_) => panic!("Expected Ok"),
1406 }
1407
1408 let io_err = Error::io("test");
1410 match io_err.into_external() {
1411 Err(Error::IO { .. }) => {}
1412 _ => panic!("Expected Err with IO variant"),
1413 }
1414 }
1415
1416 #[test]
1417 fn test_arrow_external_error_conversion() {
1418 let custom_err = MyCustomError {
1419 code: 789,
1420 message: "arrow test".to_string(),
1421 };
1422 let arrow_err = ArrowError::ExternalError(Box::new(custom_err));
1423 let lance_err: Error = arrow_err.into();
1424
1425 match lance_err {
1426 Error::External { source } => {
1427 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1428 assert_eq!(recovered.code, 789);
1429 }
1430 _ => panic!("Expected External variant, got {:?}", lance_err),
1431 }
1432 }
1433
1434 #[test]
1435 fn test_external_to_arrow_roundtrip() {
1436 let custom_err = MyCustomError {
1437 code: 999,
1438 message: "roundtrip".to_string(),
1439 };
1440 let lance_err = Error::external(Box::new(custom_err));
1441 let arrow_err: ArrowError = lance_err.into();
1442
1443 match arrow_err {
1444 ArrowError::ExternalError(source) => {
1445 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1446 assert_eq!(recovered.code, 999);
1447 }
1448 _ => panic!("Expected ExternalError variant"),
1449 }
1450 }
1451
1452 #[cfg(feature = "datafusion")]
1453 #[test]
1454 fn test_datafusion_schema_error_is_invalid_input() {
1455 use datafusion_common::Column;
1460
1461 let schema_err = datafusion_common::SchemaError::FieldNotFound {
1462 field: Box::new(Column::from_name("missing_col")),
1463 valid_fields: vec![],
1464 };
1465 let df_err =
1466 datafusion_common::DataFusionError::SchemaError(Box::new(schema_err), Box::new(None));
1467 let lance_err: Error = df_err.into();
1468
1469 match lance_err {
1470 Error::InvalidInput { .. } => {
1471 assert!(
1472 lance_err.to_string().contains("missing_col"),
1473 "expected the column name to survive in the error message, got: {lance_err}"
1474 );
1475 }
1476 _ => panic!("Expected InvalidInput variant, got {:?}", lance_err),
1477 }
1478 }
1479
1480 #[cfg(feature = "datafusion")]
1486 #[rstest::rstest]
1487 #[case::diagnostic(|inner| datafusion_common::DataFusionError::Diagnostic(
1488 Box::new(datafusion_common::Diagnostic::new_error("invalid function", None)),
1489 Box::new(inner),
1490 ))]
1491 #[case::context(|inner| datafusion_common::DataFusionError::Context(
1492 "type_coercion".to_string(),
1493 Box::new(inner),
1494 ))]
1495 #[case::collection(|inner| datafusion_common::DataFusionError::Collection(vec![inner]))]
1496 #[case::nested(|inner| datafusion_common::DataFusionError::Diagnostic(
1497 Box::new(datafusion_common::Diagnostic::new_error("invalid function", None)),
1498 Box::new(datafusion_common::DataFusionError::Context(
1499 "type_coercion".to_string(),
1500 Box::new(inner),
1501 )),
1502 ))]
1503 fn test_datafusion_wrapped_plan_error_is_invalid_input(
1504 #[case] wrap: fn(datafusion_common::DataFusionError) -> datafusion_common::DataFusionError,
1505 ) {
1506 let df_err = wrap(datafusion_common::DataFusionError::Plan(
1507 "Invalid function 'no_such_function'".to_string(),
1508 ));
1509 let lance_err = Error::from(df_err);
1510
1511 assert!(
1512 matches!(lance_err, Error::InvalidInput { .. }),
1513 "expected InvalidInput, got {lance_err:?}"
1514 );
1515 assert!(
1516 lance_err.to_string().contains("no_such_function"),
1517 "expected the function name to survive, got: {lance_err}"
1518 );
1519 }
1520
1521 #[cfg(feature = "datafusion")]
1524 #[test]
1525 fn test_datafusion_wrapped_internal_error_is_not_invalid_input() {
1526 let df_err = datafusion_common::DataFusionError::Context(
1527 "while running".to_string(),
1528 Box::new(datafusion_common::DataFusionError::Internal(
1529 "invariant violated".to_string(),
1530 )),
1531 );
1532
1533 assert!(
1534 matches!(Error::from(df_err), Error::IO { .. }),
1535 "an internal DataFusion failure must not be reported as user input"
1536 );
1537 }
1538
1539 #[cfg(feature = "datafusion")]
1542 #[test]
1543 fn test_wrapped_external_lance_error_keeps_its_category() {
1544 let df_err = datafusion_common::DataFusionError::Context(
1545 "while scanning".to_string(),
1546 Box::new(datafusion_common::DataFusionError::from(Error::io(
1547 "object store unavailable",
1548 ))),
1549 );
1550
1551 match Error::from(df_err) {
1552 Error::IO { source, .. } => assert!(
1553 source.to_string().contains("object store unavailable"),
1554 "expected the original message, got: {source}"
1555 ),
1556 other => panic!("expected the original IO error, got {other:?}"),
1557 }
1558 }
1559
1560 #[cfg(feature = "datafusion")]
1561 #[test]
1562 fn test_datafusion_external_error_conversion() {
1563 let custom_err = MyCustomError {
1564 code: 111,
1565 message: "datafusion test".to_string(),
1566 };
1567 let df_err = datafusion_common::DataFusionError::External(Box::new(custom_err));
1568 let lance_err: Error = df_err.into();
1569
1570 match lance_err {
1571 Error::External { source } => {
1572 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1573 assert_eq!(recovered.code, 111);
1574 }
1575 _ => panic!("Expected External variant"),
1576 }
1577 }
1578
1579 #[cfg(feature = "datafusion")]
1580 #[test]
1581 fn test_datafusion_arrow_external_error_conversion() {
1582 let custom_err = MyCustomError {
1584 code: 222,
1585 message: "nested test".to_string(),
1586 };
1587 let arrow_err = ArrowError::ExternalError(Box::new(custom_err));
1588 let df_err = datafusion_common::DataFusionError::ArrowError(Box::new(arrow_err), None);
1589 let lance_err: Error = df_err.into();
1590
1591 match lance_err {
1592 Error::External { source } => {
1593 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1594 assert_eq!(recovered.code, 222);
1595 }
1596 _ => panic!("Expected External variant, got {:?}", lance_err),
1597 }
1598 }
1599
1600 #[test]
1606 fn test_lance_error_roundtrip_through_arrow() {
1607 let original = Error::invalid_input("test validation error");
1608
1609 let arrow_err: ArrowError = original.into();
1611
1612 let recovered: Error = arrow_err.into();
1614
1615 match recovered {
1617 Error::InvalidInput { .. } => {
1618 assert!(recovered.to_string().contains("test validation error"));
1619 }
1620 _ => panic!("Expected InvalidInput variant, got {:?}", recovered),
1621 }
1622 }
1623
1624 #[cfg(feature = "datafusion")]
1630 #[test]
1631 fn test_lance_error_roundtrip_through_datafusion() {
1632 let original = Error::invalid_input("test validation error");
1633
1634 let df_err: datafusion_common::DataFusionError = original.into();
1636
1637 let recovered: Error = df_err.into();
1639
1640 match recovered {
1642 Error::InvalidInput { .. } => {
1643 assert!(recovered.to_string().contains("test validation error"));
1644 }
1645 _ => panic!("Expected InvalidInput variant, got {:?}", recovered),
1646 }
1647 }
1648
1649 #[cfg(feature = "datafusion")]
1655 #[test]
1656 fn test_datafusion_shared_multi_owner_preserves_type() {
1657 let custom_err = MyCustomError {
1658 code: 42,
1659 message: "shared typed error".to_string(),
1660 };
1661 let marker = datafusion_common::DataFusionError::External(Box::new(custom_err));
1662 let arc = std::sync::Arc::new(marker);
1664 let _arc2 = arc.clone();
1665 let shared = datafusion_common::DataFusionError::Shared(arc);
1666
1667 let lance_err: Error = shared.into();
1668
1669 let mut found = false;
1671 let mut src: Option<&dyn std::error::Error> = Some(&lance_err);
1672 while let Some(e) = src {
1673 if e.downcast_ref::<MyCustomError>().is_some() {
1674 found = true;
1675 break;
1676 }
1677 src = e.source();
1678 }
1679 assert!(
1680 found,
1681 "MyCustomError not found in source chain: {lance_err:?}"
1682 );
1683 }
1684
1685 #[test]
1686 fn test_backtrace_accessor() {
1687 let err = Error::io("test backtrace");
1689 let bt = err.backtrace();
1690 #[cfg(feature = "backtrace")]
1691 {
1692 let _ = bt;
1696 }
1697 #[cfg(not(feature = "backtrace"))]
1698 {
1699 assert!(bt.is_none());
1701 }
1702 }
1703
1704 #[test]
1705 fn test_backtrace_captured_when_feature_enabled() {
1706 #[cfg(feature = "backtrace")]
1715 {
1716 let err = Error::io("backtrace capture test");
1717 if std::env::var("RUST_BACKTRACE").is_ok() {
1718 assert!(
1719 err.backtrace().is_some(),
1720 "Expected a backtrace when RUST_BACKTRACE=1 and backtrace feature is enabled"
1721 );
1722 }
1723 }
1726 #[cfg(not(feature = "backtrace"))]
1727 {
1728 let err = Error::io("backtrace capture test");
1729 assert!(err.backtrace().is_none());
1730 }
1731 }
1732
1733 #[test]
1734 fn test_backtrace_returns_none_for_variants_without_location() {
1735 let err = Error::InvalidTableLocation {
1736 message: "test".to_string(),
1737 };
1738 assert!(err.backtrace().is_none());
1739
1740 let err = Error::InvalidRef {
1741 message: "test".to_string(),
1742 };
1743 assert!(err.backtrace().is_none());
1744
1745 let err = Error::Stop;
1746 assert!(err.backtrace().is_none());
1747 }
1748}