#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Multitrait(#[from] multi_trait::Error),
#[error(
"Invalid multicodec name: '{name}'\n\
The name '{name}' is not a recognized multicodec identifier.\n\
See https://github.com/multiformats/multicodec/blob/master/table.csv for valid names."
)]
InvalidName {
name: String,
},
#[error(
"Invalid multicodec code: 0x{code:x} ({code})\n\
The code 0x{code:x} is not a recognized multicodec value.\n\
See https://github.com/multiformats/multicodec/blob/master/table.csv for valid codes."
)]
InvalidValue {
code: u64,
},
#[error(
"Cannot convert negative value {value} to multicodec\n\
Codec codes must be non-negative integers. The value {value} is negative.\n\
Use a non-negative value or an unsigned integer type (u8, u16, u32, u64)."
)]
NegativeValue {
value: i64,
},
}
impl Error {
pub fn invalid_name(name: impl Into<String>) -> Self {
Self::InvalidName { name: name.into() }
}
pub fn invalid_value(code: u64) -> Self {
Self::InvalidValue { code }
}
pub fn negative_value(value: i64) -> Self {
Self::NegativeValue { value }
}
pub fn kind(&self) -> &str {
match self {
Self::Multitrait(_) => "Multitrait",
Self::InvalidName { .. } => "InvalidName",
Self::InvalidValue { .. } => "InvalidValue",
Self::NegativeValue { .. } => "NegativeValue",
}
}
pub fn context(&self) -> String {
match self {
Self::Multitrait(e) => format!("Multitrait error: {}", e),
Self::InvalidName { name } => format!("Invalid name: '{}'", name),
Self::InvalidValue { code } => format!("Invalid code: 0x{:x} ({})", code, code),
Self::NegativeValue { value } => format!("Negative value: {}", value),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_name_error() {
let err = Error::invalid_name("bad-codec");
assert!(matches!(err, Error::InvalidName { .. }));
assert_eq!(err.kind(), "InvalidName");
assert!(err.context().contains("bad-codec"));
assert!(err.to_string().contains("bad-codec"));
}
#[test]
fn test_invalid_value_error() {
let err = Error::invalid_value(0xDEAD);
assert!(matches!(err, Error::InvalidValue { .. }));
assert_eq!(err.kind(), "InvalidValue");
assert!(err.context().contains("dead"));
assert!(err.to_string().contains("0xdead"));
}
#[test]
fn test_negative_value_error() {
let err = Error::negative_value(-42);
assert!(matches!(err, Error::NegativeValue { .. }));
assert_eq!(err.kind(), "NegativeValue");
assert!(err.context().contains("-42"));
assert!(err.to_string().contains("-42"));
}
#[test]
fn test_error_messages_have_context() {
let err = Error::invalid_name("unknown");
let msg = err.to_string();
assert!(msg.contains("unknown"));
assert!(msg.contains("multicodec"));
let err = Error::invalid_value(99999);
let msg = err.to_string();
assert!(msg.contains("99999"));
assert!(msg.contains("0x"));
}
#[test]
fn test_error_kind_uniqueness() {
let errors = [
Error::invalid_name("test"),
Error::invalid_value(123),
Error::negative_value(-1),
];
let kinds: Vec<_> = errors.iter().map(|e| e.kind()).collect();
assert_eq!(kinds.len(), 3);
assert!(kinds
.iter()
.all(|k| kinds.iter().filter(|x| *x == k).count() == 1));
}
}