use std::fmt;
use iroh_blobs::Hash;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CacheError {
#[error("blob {hash} not found in cache or at origin")]
NotFound {
hash: Hash,
},
#[error("origin returned blob {actual} when {expected} was requested")]
HashMismatch {
expected: Hash,
actual: Hash,
},
#[error("origin bytes failed bao verification against {expected}")]
VerifyFailed {
expected: Hash,
},
#[error("blob {hash} exceeds max_blob_size of {limit_bytes} bytes")]
BlobTooLarge {
hash: Hash,
limit_bytes: u64,
},
#[error("cache miss for {hash} but no origin is configured")]
NoOrigin {
hash: Hash,
},
#[error("origin error fetching {hash}")]
OriginError {
hash: Hash,
#[source]
source: anyhow::Error,
},
#[error("store error")]
Store(#[source] anyhow::Error),
#[error("admit feed failed")]
Feed(#[source] anyhow::Error),
#[error("internal fault")]
Internal(#[source] anyhow::Error),
#[error("evicted-hash set full ({limit} entries); refusing to add more")]
EvictionLimitExceeded {
limit: usize,
},
}
impl CacheError {
pub fn display_chain(&self) -> ErrorChain<'_> {
ErrorChain::new(self)
}
pub fn origin_error_kind(&self) -> Option<&OriginError> {
let Self::OriginError { source, .. } = self else {
return None;
};
source
.chain()
.find_map(|cause| cause.downcast_ref::<OriginError>())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SupportedEncoding {
Gzip,
Zstd,
}
impl fmt::Display for SupportedEncoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Gzip => f.write_str("gzip"),
Self::Zstd => f.write_str("zstd"),
}
}
}
#[derive(Debug, Error)]
pub enum OriginError {
#[error("unsupported Content-Encoding: {encoding}")]
UnsupportedEncoding {
encoding: Box<str>,
},
#[error("malformed Content-Encoding header (non-ASCII or unprintable)")]
MalformedEncoding,
#[error("failed to decompress {encoding} response body")]
DecompressionFailed {
encoding: SupportedEncoding,
#[source]
source: std::io::Error,
},
}
#[derive(Debug, Clone, Copy)]
pub struct ErrorChain<'a>(&'a (dyn std::error::Error + 'static));
impl<'a> ErrorChain<'a> {
pub const fn new(err: &'a (dyn std::error::Error + 'static)) -> Self {
Self(err)
}
}
impl fmt::Display for ErrorChain<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)?;
let mut cause = self.0.source();
while let Some(err) = cause {
write!(f, ": {err}")?;
cause = err.source();
}
Ok(())
}
}
pub type CacheResult<T> = std::result::Result<T, CacheError>;
#[derive(Debug, Error)]
pub enum OriginPullError {
#[error(transparent)]
Transient(anyhow::Error),
#[error(transparent)]
Permanent(anyhow::Error),
}
impl OriginPullError {
pub const fn is_transient(&self) -> bool {
matches!(self, Self::Transient(_))
}
pub fn into_inner(self) -> anyhow::Error {
match self {
Self::Transient(e) | Self::Permanent(e) => e,
}
}
pub(crate) fn map_inner<F>(self, f: F) -> Self
where
F: FnOnce(anyhow::Error) -> anyhow::Error,
{
match self {
Self::Transient(e) => Self::Transient(f(e)),
Self::Permanent(e) => Self::Permanent(f(e)),
}
}
}
#[cfg(test)]
mod tests;