use std::error::Error;
use object_store::path::Path;
use slatedb_common::retry::NonRetryable;
pub const MIRROR_STORE_NAME: &str = "ObjectStoreMirror";
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum MirrorError {
#[error("object is not in the local mirror. path=`{path}`")]
NotLocal { path: Path },
#[error("local mirror I/O failed")]
Local { source: std::io::Error },
#[error("write reached the remote store but did not finish locally. path=`{path}`")]
WriteCommitted {
path: Path,
source: Box<dyn Error + Send + Sync>,
},
#[error("mirror policy failed")]
Policy {
source: Box<dyn Error + Send + Sync>,
},
#[error("operation not supported by the mirror. operation=`{operation}`")]
Unsupported { operation: &'static str },
#[error("invalid mirror configuration. message=`{message}`")]
InvalidConfig { message: String },
}
impl MirrorError {
pub fn find<'a>(err: &'a (dyn Error + 'static)) -> Option<&'a MirrorError> {
let mut current = Some(err);
while let Some(err) = current {
if let Some(mirror_err) = err.downcast_ref::<MirrorError>() {
return Some(mirror_err);
}
if let Some(mirror_err) = err
.downcast_ref::<std::io::Error>()
.and_then(|err| err.get_ref())
.and_then(|inner| inner.downcast_ref::<MirrorError>())
{
return Some(mirror_err);
}
current = err.source();
}
None
}
}
impl From<MirrorError> for object_store::Error {
fn from(err: MirrorError) -> Self {
object_store::Error::Generic {
store: MIRROR_STORE_NAME,
source: Box::new(NonRetryable(Box::new(err))),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
fn not_local() -> MirrorError {
MirrorError::NotLocal {
path: Path::from("a/b.sst"),
}
}
#[test]
fn should_find_mirror_error_itself() {
let err = not_local();
assert!(matches!(
MirrorError::find(&err),
Some(MirrorError::NotLocal { .. })
));
}
#[test]
fn should_find_mirror_error_in_object_store_error() {
let err = object_store::Error::from(not_local());
assert!(matches!(
MirrorError::find(&err),
Some(MirrorError::NotLocal { .. })
));
}
#[test]
fn should_find_mirror_error_through_arc_and_io_error() {
let err = Arc::new(object_store::Error::from(not_local()));
assert!(MirrorError::find(&err).is_some());
let err = std::io::Error::other(object_store::Error::from(not_local()));
assert!(MirrorError::find(&err).is_some());
let err = std::io::Error::other(not_local());
assert!(MirrorError::find(&err).is_some());
}
#[test]
fn should_not_find_mirror_error_in_other_errors() {
let err = object_store::Error::Generic {
store: "S3",
source: Box::new(std::io::Error::other("timeout")),
};
assert!(MirrorError::find(&err).is_none());
}
}