use std::error::Error as StdError;
pub type BoxError = Box<dyn StdError + Send + Sync + 'static>;
#[derive(Debug, thiserror::Error)]
pub enum ObjectStoreError {
#[error("object not found: {0}")]
NotFound(String),
#[error("access denied: {0}")]
AccessDenied(String),
#[error("precondition failed: {0}")]
PreconditionFailed(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("upload exceeds backend size limit ({})", format_byte_limit(*limit_bytes))]
PayloadTooLarge {
limit_bytes: u64,
},
#[error("range {}..{} not satisfiable for key `{key}`", requested.start, requested.end)]
RangeNotSatisfiable {
key: String,
requested: std::ops::Range<u64>,
},
#[error("network error: {0}")]
Network(#[source] BoxError),
#[error("{0}")]
Unsupported(String),
#[error(transparent)]
Other(BoxError),
}
fn format_byte_limit(bytes: u64) -> String {
const GIB: u64 = 1 << 30;
const MIB: u64 = 1 << 20;
if bytes >= GIB && bytes.is_multiple_of(GIB) {
format!("{} GiB", bytes / GIB)
} else if bytes >= MIB && bytes.is_multiple_of(MIB) {
format!("{} MiB", bytes / MIB)
} else {
format!("{bytes} B")
}
}
pub(crate) fn other_boxed<E: StdError + Send + Sync + 'static>(e: E) -> ObjectStoreError {
ObjectStoreError::Other(Box::new(e))
}
pub(crate) fn network_boxed<E: StdError + Send + Sync + 'static>(e: E) -> ObjectStoreError {
ObjectStoreError::Network(Box::new(e))
}
#[cfg(test)]
mod tests {
use super::*;
fn boxed_io(message: &str) -> BoxError {
Box::new(std::io::Error::other(message.to_string()))
}
#[test]
fn display_names_the_key() {
assert_eq!(
ObjectStoreError::NotFound("a/b".into()).to_string(),
"object not found: a/b"
);
assert_eq!(
ObjectStoreError::AccessDenied("a/b".into()).to_string(),
"access denied: a/b"
);
assert_eq!(
ObjectStoreError::PreconditionFailed("a/b".into()).to_string(),
"precondition failed: a/b"
);
assert_eq!(
ObjectStoreError::Conflict("a/b".into()).to_string(),
"conflict: a/b"
);
}
#[test]
fn network_preserves_source_chain() {
let err = ObjectStoreError::Network(boxed_io("dns failure"));
assert_eq!(err.to_string(), "network error: dns failure");
let source = err.source().expect("Network exposes its #[source]");
assert_eq!(source.to_string(), "dns failure");
}
#[test]
fn other_is_transparent() {
let err = ObjectStoreError::Other(boxed_io("boom"));
assert_eq!(err.to_string(), "boom");
}
#[test]
fn payload_too_large_renders_gib_when_exact() {
let err = ObjectStoreError::PayloadTooLarge {
limit_bytes: 5 * (1 << 30),
};
assert_eq!(err.to_string(), "upload exceeds backend size limit (5 GiB)");
}
#[test]
fn payload_too_large_renders_mib_when_not_a_clean_gib() {
let err = ObjectStoreError::PayloadTooLarge {
limit_bytes: 5_000 * (1 << 20),
};
assert_eq!(
err.to_string(),
"upload exceeds backend size limit (5000 MiB)"
);
}
#[test]
fn payload_too_large_falls_back_to_raw_bytes() {
let err = ObjectStoreError::PayloadTooLarge { limit_bytes: 1_234 };
assert_eq!(
err.to_string(),
"upload exceeds backend size limit (1234 B)"
);
}
#[test]
fn range_not_satisfiable_names_key_and_range() {
let err = ObjectStoreError::RangeNotSatisfiable {
key: "packs/abc.pack".to_string(),
requested: 100..200,
};
assert_eq!(
err.to_string(),
"range 100..200 not satisfiable for key `packs/abc.pack`"
);
}
}