#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Multicodec(#[from] multi_codec::Error),
#[error(transparent)]
Multiutil(#[from] multi_util::Error),
#[error(
"Missing hash data\n\
The multihash builder requires hash digest data.\n\
Call Builder::with_hash() before build()."
)]
MissingHash,
#[error(
"Unsupported hash algorithm: {codec:?}\n\
The codec {codec:?} is not a supported cryptographic hash algorithm.\n\
See HASH_CODECS or SAFE_HASH_CODECS for supported algorithms."
)]
UnsupportedHash {
codec: multi_codec::Codec,
},
#[error(
"Invalid hash digest length for {algorithm:?}\n\
Expected {expected} bytes for {algorithm:?}, but got {actual} bytes.\n\
Ensure the hash digest matches the algorithm's output size."
)]
InvalidDigestLength {
algorithm: multi_codec::Codec,
expected: usize,
actual: usize,
},
#[error("Hash computation failed for {algorithm:?}: {message}")]
HashComputeFailed {
algorithm: multi_codec::Codec,
message: String,
},
}
impl Error {
pub fn unsupported_hash(codec: multi_codec::Codec) -> Self {
Self::UnsupportedHash { codec }
}
pub fn invalid_digest_length(
algorithm: multi_codec::Codec,
expected: usize,
actual: usize,
) -> Self {
Self::InvalidDigestLength {
algorithm,
expected,
actual,
}
}
pub fn hash_compute_failed(algorithm: multi_codec::Codec, message: impl Into<String>) -> Self {
Self::HashComputeFailed {
algorithm,
message: message.into(),
}
}
pub fn kind(&self) -> &str {
match self {
Self::Multicodec(_) => "Multicodec",
Self::Multiutil(_) => "Multiutil",
Self::MissingHash => "MissingHash",
Self::UnsupportedHash { .. } => "UnsupportedHash",
Self::InvalidDigestLength { .. } => "InvalidDigestLength",
Self::HashComputeFailed { .. } => "HashComputeFailed",
}
}
pub fn context(&self) -> String {
match self {
Self::Multicodec(e) => format!("Multicodec error: {}", e),
Self::Multiutil(e) => format!("Multiutil error: {}", e),
Self::MissingHash => "Missing hash data in builder".to_string(),
Self::UnsupportedHash { codec } => format!("Unsupported hash: {:?}", codec),
Self::InvalidDigestLength {
algorithm,
expected,
actual,
} => format!(
"Invalid digest length for {:?}: expected {}, got {}",
algorithm, expected, actual
),
Self::HashComputeFailed { algorithm, message } => {
format!("Hash computation failed for {:?}: {}", algorithm, message)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use multi_codec::Codec;
#[test]
fn test_missing_hash_error() {
let err = Error::MissingHash;
assert_eq!(err.kind(), "MissingHash");
assert!(err.to_string().contains("Missing hash data"));
}
#[test]
fn test_unsupported_hash_error() {
let err = Error::unsupported_hash(Codec::Identity);
assert_eq!(err.kind(), "UnsupportedHash");
let msg = err.to_string();
assert!(!msg.is_empty());
assert!(msg.len() > 10);
let context = err.context();
assert!(!context.is_empty());
}
#[test]
fn test_invalid_digest_length_error() {
let err = Error::invalid_digest_length(Codec::Sha2256, 32, 16);
assert_eq!(err.kind(), "InvalidDigestLength");
let msg = err.to_string();
assert!(msg.contains("32"));
assert!(msg.contains("16"));
}
#[test]
fn test_hash_compute_failed_error() {
let err = Error::hash_compute_failed(Codec::Sha2256, "test failure");
assert_eq!(err.kind(), "HashComputeFailed");
assert!(err.to_string().contains("test failure"));
}
#[test]
fn test_error_kind_uniqueness() {
let errors = [
Error::MissingHash,
Error::unsupported_hash(Codec::Identity),
Error::invalid_digest_length(Codec::Sha2256, 32, 16),
Error::hash_compute_failed(Codec::Sha2256, "test"),
];
let kinds: Vec<_> = errors.iter().map(|e| e.kind()).collect();
assert_eq!(kinds.len(), 4);
for (i, k1) in kinds.iter().enumerate() {
for (j, k2) in kinds.iter().enumerate() {
if i != j {
assert_ne!(k1, k2);
}
}
}
}
#[test]
fn test_error_context_informative() {
let err = Error::unsupported_hash(Codec::Sha2256);
let context = err.context();
assert!(!context.is_empty());
assert!(context.contains("Sha2256") || context.contains("sha2-256"));
let err = Error::invalid_digest_length(Codec::Sha2512, 64, 32);
let context = err.context();
assert!(context.contains("64"));
assert!(context.contains("32"));
}
#[test]
fn test_error_is_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<Error>();
assert_sync::<Error>();
}
}