#[must_use]
pub fn bytes_to_hex(bytes: &[u8]) -> String {
cardanowall::hex::encode(bytes)
}
pub fn hex_to_bytes(input: &str) -> Result<Vec<u8>, String> {
let clean = input
.strip_prefix("0x")
.or_else(|| input.strip_prefix("0X"))
.unwrap_or(input);
if !clean.len().is_multiple_of(2) {
return Err(format!("hex value has odd length: {input}"));
}
if !clean.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(format!("hex value contains non-hex chars: {input}"));
}
hex::decode(clean).map_err(|e| format!("hex value could not be decoded: {input} ({e})"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips_lowercase() {
let bytes = [0xde, 0xad, 0xbe, 0xef];
assert_eq!(bytes_to_hex(&bytes), "deadbeef");
assert_eq!(hex_to_bytes("deadbeef").unwrap(), bytes);
}
#[test]
fn accepts_0x_prefix_and_uppercase() {
assert_eq!(hex_to_bytes("0xDEAD").unwrap(), vec![0xde, 0xad]);
assert_eq!(hex_to_bytes("0XdeAd").unwrap(), vec![0xde, 0xad]);
}
#[test]
fn rejects_odd_length() {
assert!(hex_to_bytes("abc").unwrap_err().contains("odd length"));
}
#[test]
fn rejects_non_hex() {
assert!(hex_to_bytes("zzzz").unwrap_err().contains("non-hex"));
}
}