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                    operation: _,
136                    implementer: _,
137                } => PyNotImplementedError::new_err(print_with_debug(err)),
138                object_store::Error::PermissionDenied { path: _, source: _ } => {
139                    PermissionDeniedError::new_err(print_with_debug(err))
140                }
141                object_store::Error::Unauthenticated { path: _, source: _ } => {
142                    UnauthenticatedError::new_err(print_with_debug(err))
143                }
144                object_store::Error::UnknownConfigurationKey { store: _, key: _ } => {
145                    UnknownConfigurationKeyError::new_err(print_with_debug(err))
146                }
147                _ => GenericError::new_err(print_with_debug(err)),
148            },
149            PyObjectStoreError::IOError(err) => PyIOError::new_err(err),
150        }
151    }
152}
153
154fn print_with_debug(err: &object_store::Error) -> String {
155    // #? gives "pretty-printing" for debug
156    // https://doc.rust-lang.org/std/fmt/trait.Debug.html
157    format!("{err}\n\nDebug source:\n{err:#?}")
158}
159
160impl<'a, 'py> From<CastError<'a, 'py>> for PyObjectStoreError {
161    fn from(other: CastError<'a, 'py>) -> Self {
162        Self::PyErr(PyValueError::new_err(format!(
163            "Could not downcast: {other}",
164        )))
165    }
166}
167
168/// A type wrapper around `Result<T, PyObjectStoreError>`.
169pub type PyObjectStoreResult<T> = Result<T, PyObjectStoreError>;
170
171/// A specialized `Error` for object store-related errors
172///
173/// Vendored from upstream to handle our vendored URL parsing
174#[derive(Debug, thiserror::Error)]
175pub(crate) enum ParseUrlError {
176    #[error(
177        "Unknown url scheme cannot be parsed into storage location: {}",
178        scheme
179    )]
180    UnknownUrlScheme { scheme: String },
181
182    #[error("URL did not match any known pattern for scheme: {}", url)]
183    UrlNotRecognised { url: String },
184}
185
186impl From<ParseUrlError> for object_store::Error {
187    fn from(source: ParseUrlError) -> Self {
188        Self::Generic {
189            store: "S3",
190            source: Box::new(source),
191        }
192    }
193}