Skip to main content

pyo3_object_store/
error.rs

1//! Contains the [`PyObjectStoreError`], the error enum returned by all fallible functions in this
2//! crate.
3
4use pyo3::exceptions::{PyFileNotFoundError, PyIOError, PyNotImplementedError, PyValueError};
5use pyo3::prelude::*;
6use pyo3::{create_exception, CastError};
7use thiserror::Error;
8
9// Base exception
10// Note that this is named `BaseError` instead of `ObstoreError` to not leak the name "obstore" to
11// other Rust-Python libraries using pyo3-object_store.
12create_exception!(
13    pyo3_object_store,
14    BaseError,
15    pyo3::exceptions::PyException,
16    "The base Python-facing exception from which all other errors subclass."
17);
18
19// Subclasses from base exception
20create_exception!(
21    pyo3_object_store,
22    GenericError,
23    BaseError,
24    "A Python-facing exception wrapping [object_store::Error::Generic]."
25);
26create_exception!(
27    pyo3_object_store,
28    NotFoundError,
29    BaseError,
30    "A Python-facing exception wrapping [object_store::Error::NotFound]."
31);
32create_exception!(
33    pyo3_object_store,
34    InvalidPathError,
35    BaseError,
36    "A Python-facing exception wrapping [object_store::Error::InvalidPath]."
37);
38create_exception!(
39    pyo3_object_store,
40    JoinError,
41    BaseError,
42    "A Python-facing exception wrapping [object_store::Error::JoinError]."
43);
44create_exception!(
45    pyo3_object_store,
46    NotSupportedError,
47    BaseError,
48    "A Python-facing exception wrapping [object_store::Error::NotSupported]."
49);
50create_exception!(
51    pyo3_object_store,
52    AlreadyExistsError,
53    BaseError,
54    "A Python-facing exception wrapping [object_store::Error::AlreadyExists]."
55);
56create_exception!(
57    pyo3_object_store,
58    PreconditionError,
59    BaseError,
60    "A Python-facing exception wrapping [object_store::Error::Precondition]."
61);
62create_exception!(
63    pyo3_object_store,
64    NotModifiedError,
65    BaseError,
66    "A Python-facing exception wrapping [object_store::Error::NotModified]."
67);
68create_exception!(
69    pyo3_object_store,
70    PermissionDeniedError,
71    BaseError,
72    "A Python-facing exception wrapping [object_store::Error::PermissionDenied]."
73);
74create_exception!(
75    pyo3_object_store,
76    UnauthenticatedError,
77    BaseError,
78    "A Python-facing exception wrapping [object_store::Error::Unauthenticated]."
79);
80create_exception!(
81    pyo3_object_store,
82    UnknownConfigurationKeyError,
83    BaseError,
84    "A Python-facing exception wrapping [object_store::Error::UnknownConfigurationKey]."
85);
86
87/// The Error variants returned by this crate.
88#[derive(Error, Debug)]
89#[non_exhaustive]
90pub enum PyObjectStoreError {
91    /// A wrapped [object_store::Error]
92    #[error(transparent)]
93    ObjectStoreError(#[from] object_store::Error),
94
95    /// A wrapped [PyErr]
96    #[error(transparent)]
97    PyErr(#[from] PyErr),
98
99    /// A wrapped [std::io::Error]
100    #[error(transparent)]
101    IOError(#[from] std::io::Error),
102}
103
104impl From<PyObjectStoreError> for PyErr {
105    fn from(error: PyObjectStoreError) -> Self {
106        match error {
107            PyObjectStoreError::PyErr(err) => err,
108            PyObjectStoreError::ObjectStoreError(ref err) => match err {
109                object_store::Error::Generic {
110                    store: _,
111                    source: _,
112                } => GenericError::new_err(print_with_debug(err)),
113                object_store::Error::NotFound { path: _, source: _ } => {
114                    PyFileNotFoundError::new_err(print_with_debug(err))
115                }
116                object_store::Error::InvalidPath { source: _ } => {
117                    InvalidPathError::new_err(print_with_debug(err))
118                }
119                object_store::Error::JoinError { source: _ } => {
120                    JoinError::new_err(print_with_debug(err))
121                }
122                object_store::Error::NotSupported { source: _ } => {
123                    NotSupportedError::new_err(print_with_debug(err))
124                }
125                object_store::Error::AlreadyExists { path: _, source: _ } => {
126                    AlreadyExistsError::new_err(print_with_debug(err))
127                }
128                object_store::Error::Precondition { path: _, source: _ } => {
129                    PreconditionError::new_err(print_with_debug(err))
130                }
131                object_store::Error::NotModified { path: _, source: _ } => {
132                    NotModifiedError::new_err(print_with_debug(err))
133                }
134                object_store::Error::NotImplemented => {
135                    PyNotImplementedError::new_err(print_with_debug(err))
136                }
137                object_store::Error::PermissionDenied { path: _, source: _ } => {
138                    PermissionDeniedError::new_err(print_with_debug(err))
139                }
140                object_store::Error::Unauthenticated { path: _, source: _ } => {
141                    UnauthenticatedError::new_err(print_with_debug(err))
142                }
143                object_store::Error::UnknownConfigurationKey { store: _, key: _ } => {
144                    UnknownConfigurationKeyError::new_err(print_with_debug(err))
145                }
146                _ => GenericError::new_err(print_with_debug(err)),
147            },
148            PyObjectStoreError::IOError(err) => PyIOError::new_err(err),
149        }
150    }
151}
152
153fn print_with_debug(err: &object_store::Error) -> String {
154    // #? gives "pretty-printing" for debug
155    // https://doc.rust-lang.org/std/fmt/trait.Debug.html
156    format!("{err}\n\nDebug source:\n{err:#?}")
157}
158
159impl<'a, 'py> From<CastError<'a, 'py>> for PyObjectStoreError {
160    fn from(other: CastError<'a, 'py>) -> Self {
161        Self::PyErr(PyValueError::new_err(format!(
162            "Could not downcast: {other}",
163        )))
164    }
165}
166
167/// A type wrapper around `Result<T, PyObjectStoreError>`.
168pub type PyObjectStoreResult<T> = Result<T, PyObjectStoreError>;
169
170/// A specialized `Error` for object store-related errors
171///
172/// Vendored from upstream to handle our vendored URL parsing
173#[derive(Debug, thiserror::Error)]
174pub(crate) enum ParseUrlError {
175    #[error(
176        "Unknown url scheme cannot be parsed into storage location: {}",
177        scheme
178    )]
179    UnknownUrlScheme { scheme: String },
180
181    #[error("URL did not match any known pattern for scheme: {}", url)]
182    UrlNotRecognised { url: String },
183}
184
185impl From<ParseUrlError> for object_store::Error {
186    fn from(source: ParseUrlError) -> Self {
187        Self::Generic {
188            store: "S3",
189            source: Box::new(source),
190        }
191    }
192}