1use thiserror::Error;
2use xet_client::ClientError;
3use xet_core_structures::CoreError;
4use xet_data::DataError;
5use xet_data::file_reconstruction::FileReconstructionError;
6use xet_data::progress_tracking::UniqueID;
7use xet_runtime::RuntimeError;
8
9#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum XetError {
17 #[error("Keyboard interrupt (SIGINT)")]
20 KeyboardInterrupt,
21
22 #[error("User cancelled: {0}")]
24 UserCancelled(String),
25
26 #[error("Previous task error: {0}")]
28 PreviousTaskError(String),
29
30 #[error("Task error: {0}")]
32 TaskError(String),
33
34 #[error("Already completed")]
36 AlreadyCompleted,
37
38 #[error("Invalid task ID: {0}")]
40 InvalidTaskID(UniqueID),
41
42 #[error("Authentication error: {0}")]
45 Authentication(String),
46
47 #[error("Network error: {0}")]
49 Network(String),
50
51 #[error("Timeout: {0}")]
53 Timeout(String),
54
55 #[error("Not found: {0}")]
57 NotFound(String),
58
59 #[error("Data integrity error: {0}")]
61 DataIntegrity(String),
62
63 #[error("Configuration error: {0}")]
65 Configuration(String),
66
67 #[error("I/O error: {0}")]
69 Io(String),
70
71 #[error("Operation cancelled: {0}")]
73 Cancelled(String),
74
75 #[error("Internal error: {0}")]
77 Internal(String),
78
79 #[error("Wrong runtime mode: {0}")]
81 WrongRuntimeMode(String),
82}
83
84impl XetError {
85 pub fn other(msg: impl std::fmt::Display) -> Self {
86 Self::Internal(msg.to_string())
87 }
88
89 pub fn wrong_mode(msg: impl std::fmt::Display) -> Self {
90 Self::WrongRuntimeMode(msg.to_string())
91 }
92
93 fn from_runtime_error_ref(re: &RuntimeError) -> Self {
94 match re {
95 RuntimeError::KeyboardInterrupt => XetError::KeyboardInterrupt,
96 RuntimeError::TaskCanceled(msg) => XetError::Cancelled(format!("Task cancelled: {msg}")),
97 RuntimeError::InvalidRuntime(_) => XetError::WrongRuntimeMode(re.to_string()),
98 _ => XetError::Internal(re.to_string()),
99 }
100 }
101
102 fn from_core_error_ref(fe: &CoreError) -> Self {
103 match fe {
104 CoreError::Io(_) => XetError::Io(fe.to_string()),
105 CoreError::ShardNotFound(_) | CoreError::FileNotFound(_) => XetError::NotFound(fe.to_string()),
106 CoreError::HashMismatch
107 | CoreError::TruncatedHashCollision(_)
108 | CoreError::InvalidShard(_)
109 | CoreError::ShardVersion(_)
110 | CoreError::ChunkHeaderParse
111 | CoreError::MalformedData(_)
112 | CoreError::CompressionError(_) => XetError::DataIntegrity(fe.to_string()),
113 CoreError::InvalidRange | CoreError::InvalidArguments | CoreError::BadFilename(_) => {
114 XetError::Configuration(fe.to_string())
115 },
116 CoreError::RuntimeError(re) => XetError::from_runtime_error_ref(re),
117 _ => XetError::Internal(fe.to_string()),
118 }
119 }
120
121 fn from_client_error_ref(ce: &ClientError) -> Self {
122 match ce {
123 ClientError::AuthError(_) | ClientError::PresignedUrlExpirationError | ClientError::CredentialHelper(_) => {
124 XetError::Authentication(ce.to_string())
125 },
126 ClientError::ReqwestError(e, _) if e.is_timeout() => XetError::Timeout(ce.to_string()),
127 ClientError::ReqwestError(_, _) | ClientError::ReqwestMiddlewareError(_) => {
128 XetError::Network(ce.to_string())
129 },
130 ClientError::FileNotFound(_) | ClientError::XORBNotFound(_) => XetError::NotFound(ce.to_string()),
131 ClientError::ConfigurationError(_)
132 | ClientError::InvalidArguments
133 | ClientError::InvalidRange
134 | ClientError::InvalidShardKey(_)
135 | ClientError::InvalidKey(_)
136 | ClientError::InvalidRepoType(_) => XetError::Configuration(ce.to_string()),
137 ClientError::IOError(_) => XetError::Io(ce.to_string()),
138 ClientError::FormatError(fe) => XetError::from_core_error_ref(fe),
139 _ => XetError::Internal(ce.to_string()),
140 }
141 }
142
143 fn from_file_reconstruction_error_ref(fre: &FileReconstructionError) -> Self {
144 match fre {
145 FileReconstructionError::ClientError(ce) => XetError::from_client_error_ref(ce),
146 FileReconstructionError::IoError(_) => XetError::Io(fre.to_string()),
147 FileReconstructionError::RuntimeError(re) => XetError::from_runtime_error_ref(re),
148 FileReconstructionError::TaskJoinError(je) if je.is_cancelled() => {
149 XetError::Cancelled(format!("Task cancelled: {je}"))
150 },
151 FileReconstructionError::TaskJoinError(je) => XetError::Internal(format!("Task join error: {je}")),
152 FileReconstructionError::ConfigurationError(_) => XetError::Configuration(fre.to_string()),
153 FileReconstructionError::CorruptedReconstruction(_) => XetError::DataIntegrity(fre.to_string()),
154 _ => XetError::Internal(fre.to_string()),
155 }
156 }
157
158 fn from_data_error_ref(de: &DataError) -> Self {
159 match de {
160 DataError::AuthError(_) => XetError::Authentication(de.to_string()),
161 DataError::ClientError(ce) => XetError::from_client_error_ref(ce),
162 DataError::FormatError(fe) => XetError::from_core_error_ref(fe),
163 DataError::IOError(_) => XetError::Io(de.to_string()),
164 DataError::RuntimeError(re) => XetError::from_runtime_error_ref(re),
165 DataError::FileQueryPolicyError(_)
166 | DataError::CASConfigError(_)
167 | DataError::ShardConfigError(_)
168 | DataError::DedupConfigError(_)
169 | DataError::ParameterError(_)
170 | DataError::DeprecatedError(_) => XetError::Configuration(de.to_string()),
171 DataError::HashNotFound => XetError::NotFound(de.to_string()),
172 DataError::HashStringParsingFailure(_) => XetError::DataIntegrity(de.to_string()),
173 DataError::InvalidOperation(_) => XetError::Configuration(de.to_string()),
174 DataError::FileReconstructionError(fre) => XetError::from_file_reconstruction_error_ref(fre),
175 _ => XetError::Internal(de.to_string()),
176 }
177 }
178}
179
180impl From<RuntimeError> for XetError {
183 fn from(e: RuntimeError) -> Self {
184 XetError::from_runtime_error_ref(&e)
185 }
186}
187
188impl From<CoreError> for XetError {
189 fn from(e: CoreError) -> Self {
190 XetError::from_core_error_ref(&e)
191 }
192}
193
194impl From<ClientError> for XetError {
195 fn from(e: ClientError) -> Self {
196 XetError::from_client_error_ref(&e)
197 }
198}
199
200impl From<DataError> for XetError {
201 fn from(e: DataError) -> Self {
202 XetError::from_data_error_ref(&e)
203 }
204}
205
206impl From<FileReconstructionError> for XetError {
207 fn from(e: FileReconstructionError) -> Self {
208 XetError::from_file_reconstruction_error_ref(&e)
209 }
210}
211
212impl From<std::io::Error> for XetError {
215 fn from(e: std::io::Error) -> Self {
216 XetError::Io(e.to_string())
217 }
218}
219
220impl From<tokio::task::JoinError> for XetError {
221 fn from(e: tokio::task::JoinError) -> Self {
222 if e.is_cancelled() {
223 XetError::Cancelled(format!("Task cancelled: {e}"))
224 } else {
225 XetError::Internal(format!("Task join error: {e}"))
226 }
227 }
228}
229
230impl From<tokio::sync::AcquireError> for XetError {
231 fn from(e: tokio::sync::AcquireError) -> Self {
232 XetError::Cancelled(format!("Semaphore closed: {e}"))
233 }
234}
235
236impl<T> From<std::sync::PoisonError<std::sync::MutexGuard<'_, T>>> for XetError {
237 fn from(e: std::sync::PoisonError<std::sync::MutexGuard<'_, T>>) -> Self {
238 XetError::Internal(format!("Mutex poisoned: {e}"))
239 }
240}
241
242impl<T> From<std::sync::PoisonError<std::sync::RwLockWriteGuard<'_, T>>> for XetError {
243 fn from(e: std::sync::PoisonError<std::sync::RwLockWriteGuard<'_, T>>) -> Self {
244 XetError::Internal(format!("RwLock write poisoned: {e}"))
245 }
246}
247
248impl<T> From<std::sync::PoisonError<std::sync::RwLockReadGuard<'_, T>>> for XetError {
249 fn from(e: std::sync::PoisonError<std::sync::RwLockReadGuard<'_, T>>) -> Self {
250 XetError::Internal(format!("RwLock read poisoned: {e}"))
251 }
252}
253
254#[cfg(feature = "python")]
257mod py_exceptions {
258 pyo3::create_exception!(hf_xet, XetAuthenticationError, pyo3::exceptions::PyPermissionError);
260
261 pyo3::create_exception!(hf_xet, XetObjectNotFoundError, pyo3::exceptions::PyFileNotFoundError);
263
264 pub fn register_exceptions(m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> {
269 use pyo3::types::PyModuleMethods;
270
271 m.add("XetAuthenticationError", m.py().get_type::<XetAuthenticationError>())?;
272 m.add("XetObjectNotFoundError", m.py().get_type::<XetObjectNotFoundError>())?;
273 Ok(())
274 }
275}
276
277#[cfg(feature = "python")]
278pub use py_exceptions::{XetAuthenticationError, XetObjectNotFoundError, register_exceptions};
279
280#[cfg(feature = "python")]
281impl From<XetError> for pyo3::PyErr {
282 fn from(err: XetError) -> pyo3::PyErr {
283 use pyo3::exceptions::{
284 PyConnectionError, PyKeyboardInterrupt, PyOSError, PyRuntimeError, PyTimeoutError, PyValueError,
285 };
286
287 let msg = err.to_string();
288 #[allow(unreachable_patterns)] match err {
290 XetError::KeyboardInterrupt => PyKeyboardInterrupt::new_err(msg),
291 XetError::Authentication(_) => XetAuthenticationError::new_err(msg),
292 XetError::NotFound(_) => XetObjectNotFoundError::new_err(msg),
293 XetError::Network(_) => PyConnectionError::new_err(msg),
294 XetError::Timeout(_) => PyTimeoutError::new_err(msg),
295 XetError::Io(_) => PyOSError::new_err(msg),
296 XetError::Configuration(_) | XetError::InvalidTaskID(_) => PyValueError::new_err(msg),
297 XetError::DataIntegrity(_)
298 | XetError::Internal(_)
299 | XetError::WrongRuntimeMode(_)
300 | XetError::AlreadyCompleted
301 | XetError::UserCancelled(_)
302 | XetError::PreviousTaskError(_)
303 | XetError::TaskError(_)
304 | XetError::Cancelled(_) => PyRuntimeError::new_err(msg),
305 _ => PyRuntimeError::new_err(msg),
306 }
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use xet_client::cas_client::auth::AuthError;
313 use xet_core_structures::merklehash::MerkleHash;
314
315 use super::*;
316
317 #[test]
318 fn runtime_cancelled_maps_to_cancelled() {
319 let err = XetError::from(RuntimeError::TaskCanceled("worker stopped".to_string()));
320 assert!(matches!(err, XetError::Cancelled(_)));
321 }
322
323 #[test]
324 fn runtime_keyboard_interrupt_maps_to_keyboard_interrupt() {
325 let err = XetError::from(RuntimeError::KeyboardInterrupt);
326 assert!(matches!(err, XetError::KeyboardInterrupt));
327 }
328
329 #[test]
330 fn format_not_found_maps_to_not_found() {
331 let err = XetError::from(CoreError::ShardNotFound(MerkleHash::default()));
332 assert!(matches!(err, XetError::NotFound(_)));
333 }
334
335 #[test]
336 fn format_invalid_args_maps_to_configuration() {
337 let err = XetError::from(CoreError::InvalidArguments);
338 assert!(matches!(err, XetError::Configuration(_)));
339 }
340
341 #[test]
342 fn client_auth_maps_to_authentication() {
343 let err = XetError::from(ClientError::AuthError(AuthError::TokenRefreshFailure("bad token".to_string())));
344 assert!(matches!(err, XetError::Authentication(_)));
345 }
346
347 #[test]
348 fn client_nested_format_maps_using_format_rules() {
349 let err = XetError::from(ClientError::FormatError(CoreError::InvalidRange));
350 assert!(matches!(err, XetError::Configuration(_)));
351 }
352
353 #[test]
354 fn data_nested_client_maps_using_client_rules() {
355 let err = XetError::from(DataError::ClientError(ClientError::FileNotFound(MerkleHash::default())));
356 assert!(matches!(err, XetError::NotFound(_)));
357 }
358
359 #[test]
360 fn data_runtime_cancelled_maps_to_cancelled() {
361 let err = XetError::from(DataError::RuntimeError(RuntimeError::TaskCanceled("cancelled".to_string())));
362 assert!(matches!(err, XetError::Cancelled(_)));
363 }
364
365 #[test]
366 fn presigned_url_expiration_maps_to_authentication() {
367 let err = XetError::from(ClientError::PresignedUrlExpirationError);
368 assert!(matches!(err, XetError::Authentication(_)));
369 }
370
371 #[test]
372 fn credential_helper_maps_to_authentication() {
373 let err = XetError::from(ClientError::credential_helper_error(std::io::Error::other("cred fail")));
374 assert!(matches!(err, XetError::Authentication(_)));
375 }
376
377 #[test]
378 fn client_not_found_maps_to_not_found() {
379 let err = XetError::from(ClientError::FileNotFound(MerkleHash::default()));
380 assert!(matches!(err, XetError::NotFound(_)));
381 }
382
383 #[test]
384 fn client_xorb_not_found_maps_to_not_found() {
385 let err = XetError::from(ClientError::XORBNotFound(MerkleHash::default()));
386 assert!(matches!(err, XetError::NotFound(_)));
387 }
388
389 #[test]
390 fn client_io_maps_to_io() {
391 let err = XetError::from(ClientError::IOError(std::io::Error::new(std::io::ErrorKind::NotFound, "gone")));
392 assert!(matches!(err, XetError::Io(_)));
393 }
394}