timeseries_table_format/storage/
error.rs1use std::io;
2
3use snafu::{Backtrace, prelude::*};
4
5#[derive(Debug, Snafu)]
7#[non_exhaustive]
8pub enum StorageBackendError {
9 #[snafu(display("Filesystem error: {source}"))]
11 Filesystem {
12 source: io::Error,
14 },
15}
16
17impl From<io::Error> for StorageBackendError {
18 fn from(source: io::Error) -> Self {
19 Self::Filesystem { source }
20 }
21}
22
23#[derive(Debug, Snafu)]
25#[snafu(visibility(pub(crate)))]
26#[non_exhaustive]
27pub enum StorageError {
28 #[snafu(display("Invalid table-relative storage path {path:?}: {reason}"))]
30 InvalidRelativePath {
31 path: String,
33 reason: String,
35 backtrace: Box<Backtrace>,
37 },
38
39 #[snafu(display("Path not found: {path}"))]
41 NotFound {
42 path: String,
44 source: StorageBackendError,
46 backtrace: Backtrace,
48 },
49
50 #[snafu(display("Path already exists: {path}"))]
53 AlreadyExists {
54 path: String,
56 source: StorageBackendError,
58 backtrace: Backtrace,
60 },
61
62 #[snafu(display("Storage I/O error at {path}: {source}"))]
64 OtherIo {
65 path: String,
67 source: StorageBackendError,
69 backtrace: Backtrace,
71 },
72
73 #[snafu(display(
75 "Storage operation failed at {path}: {operation_error}; cleanup also failed: {cleanup_error}"
76 ))]
77 CleanupFailed {
78 path: String,
80 #[snafu(source, backtrace)]
82 operation_error: Box<StorageError>,
83 cleanup_error: Box<StorageError>,
85 },
86}
87
88#[cfg(test)]
89mod tests {
90 use std::error::Error as _;
91
92 use snafu::ErrorCompat;
93
94 use super::*;
95
96 fn filesystem_source(error: &StorageError) -> &io::Error {
97 error
98 .source()
99 .and_then(|source| source.downcast_ref::<StorageBackendError>())
100 .and_then(|source| source.source())
101 .and_then(|source| source.downcast_ref::<io::Error>())
102 .expect("filesystem source")
103 }
104
105 #[test]
106 fn storage_errors_preserve_filesystem_sources() {
107 let cases = [
108 (
109 StorageError::NotFound {
110 path: "missing.parquet".to_string(),
111 source: io::Error::from(io::ErrorKind::NotFound).into(),
112 backtrace: Backtrace::capture(),
113 },
114 io::ErrorKind::NotFound,
115 ),
116 (
117 StorageError::AlreadyExists {
118 path: "existing.parquet".to_string(),
119 source: io::Error::from(io::ErrorKind::AlreadyExists).into(),
120 backtrace: Backtrace::capture(),
121 },
122 io::ErrorKind::AlreadyExists,
123 ),
124 (
125 StorageError::OtherIo {
126 path: "denied.parquet".to_string(),
127 source: io::Error::from(io::ErrorKind::PermissionDenied).into(),
128 backtrace: Backtrace::capture(),
129 },
130 io::ErrorKind::PermissionDenied,
131 ),
132 ];
133
134 for (error, expected_kind) in cases {
135 assert_eq!(filesystem_source(&error).kind(), expected_kind);
136 assert!(ErrorCompat::backtrace(&error).is_some());
137 }
138 }
139
140 #[test]
141 fn cleanup_failure_delegates_to_the_operation_backtrace() {
142 let operation = StorageError::OtherIo {
143 path: "data/segment.parquet".to_string(),
144 source: io::Error::other("write failed").into(),
145 backtrace: Backtrace::capture(),
146 };
147 let cleanup = StorageError::OtherIo {
148 path: "data/segment.parquet".to_string(),
149 source: io::Error::other("cleanup failed").into(),
150 backtrace: Backtrace::capture(),
151 };
152 let error = StorageError::CleanupFailed {
153 path: "data/segment.parquet".to_string(),
154 operation_error: Box::new(operation),
155 cleanup_error: Box::new(cleanup),
156 };
157
158 let wrapper_backtrace = ErrorCompat::backtrace(&error).expect("wrapper backtrace");
159 let (operation, cleanup) = match &error {
160 StorageError::CleanupFailed {
161 operation_error,
162 cleanup_error,
163 ..
164 } => (operation_error.as_ref(), cleanup_error.as_ref()),
165 _ => unreachable!(),
166 };
167 let operation_backtrace = ErrorCompat::backtrace(operation).expect("operation backtrace");
168 assert!(std::ptr::eq(wrapper_backtrace, operation_backtrace));
169 assert_eq!(filesystem_source(operation).to_string(), "write failed");
170 assert_eq!(filesystem_source(cleanup).to_string(), "cleanup failed");
171 }
172}