#[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 with_hash() or update() before try_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(
"XOF output length required for {codec:?}\n\
The {codec:?} XOF needs an output length set through Builder::output_len().\n\
Set a value from 1 to MAX_HASH_LENGTH bytes before try_build()."
)]
OutputLenRequired {
codec: multi_codec::Codec,
},
#[error(
"Invalid XOF output length for {codec:?}\n\
The requested output length {output_len} is outside the allowed\n\
range of 1 to {max} bytes for {codec:?}."
)]
OutputLenInvalid {
codec: multi_codec::Codec,
output_len: usize,
max: usize,
},
#[error("Hash computation failed for {algorithm:?}: {message}")]
HashComputeFailed {
algorithm: multi_codec::Codec,
message: String,
},
}
impl Error {
#[must_use]
pub const fn unsupported_hash(codec: multi_codec::Codec) -> Self {
Self::UnsupportedHash { codec }
}
#[must_use]
pub const fn invalid_digest_length(
algorithm: multi_codec::Codec,
expected: usize,
actual: usize,
) -> Self {
Self::InvalidDigestLength {
algorithm,
expected,
actual,
}
}
#[must_use]
pub const fn output_len_required(codec: multi_codec::Codec) -> Self {
Self::OutputLenRequired { codec }
}
#[must_use]
pub const fn output_len_invalid(
codec: multi_codec::Codec,
output_len: usize,
max: usize,
) -> Self {
Self::OutputLenInvalid {
codec,
output_len,
max,
}
}
pub fn hash_compute_failed(algorithm: multi_codec::Codec, message: impl Into<String>) -> Self {
Self::HashComputeFailed {
algorithm,
message: message.into(),
}
}
#[must_use]
pub const fn kind(&self) -> &str {
match self {
Self::Multicodec(_) => "Multicodec",
Self::Multiutil(_) => "Multiutil",
Self::MissingHash => "MissingHash",
Self::UnsupportedHash { .. } => "UnsupportedHash",
Self::InvalidDigestLength { .. } => "InvalidDigestLength",
Self::OutputLenRequired { .. } => "OutputLenRequired",
Self::OutputLenInvalid { .. } => "OutputLenInvalid",
Self::HashComputeFailed { .. } => "HashComputeFailed",
}
}
#[must_use]
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 {algorithm:?}: expected {expected}, got {actual}"
),
Self::OutputLenRequired { codec } => {
format!("XOF output length required for {codec:?}")
}
Self::OutputLenInvalid {
codec,
output_len,
max,
} => format!(
"Invalid XOF output length for {codec:?}: {output_len} is outside the allowed range of 1 to {max}"
),
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_ne!(msg, "");
assert!(msg.len() > 10);
let context = err.context();
assert_ne!(context, "");
}
#[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_output_len_required_error() {
let err = Error::output_len_required(Codec::Shake128);
assert_eq!(err.kind(), "OutputLenRequired");
assert!(err.to_string().contains("output length"));
let context = err.context();
assert_ne!(context, "");
assert!(context.contains("Shake128") || context.contains("shake-128"));
}
#[test]
fn test_output_len_invalid_error() {
let err = Error::output_len_invalid(Codec::Shake256, 0, 16 * 1024 * 1024);
assert_eq!(err.kind(), "OutputLenInvalid");
let msg = err.to_string();
assert!(msg.contains('0'));
assert!(msg.contains("16777216"));
let context = err.context();
assert_ne!(context, "");
assert!(context.contains("Shake256") || context.contains("shake-256"));
}
#[test]
fn test_error_kind_uniqueness() {
let errors = [
Error::MissingHash,
Error::unsupported_hash(Codec::Identity),
Error::invalid_digest_length(Codec::Sha2256, 32, 16),
Error::output_len_required(Codec::Shake128),
Error::output_len_invalid(Codec::Shake256, 0, 16 * 1024 * 1024),
Error::hash_compute_failed(Codec::Sha2256, "test"),
];
let kinds: Vec<_> = errors.iter().map(Error::kind).collect();
assert_eq!(kinds.len(), 6);
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_ne!(context, "");
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>();
}
}