use std::collections::BTreeMap;
use crate::state::HashAlg;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MultihashDigest {
variants: BTreeMap<HashAlg, Box<[u8]>>,
}
impl Default for MultihashDigest {
fn default() -> Self {
Self {
variants: BTreeMap::new(),
}
}
}
impl MultihashDigest {
pub fn new(variants: BTreeMap<HashAlg, Box<[u8]>>) -> crate::error::Result<Self> {
if variants.is_empty() {
return Err(crate::error::Error::EmptyMultihash);
}
Ok(Self { variants })
}
#[must_use]
pub fn from_single(alg: HashAlg, digest: impl Into<Box<[u8]>>) -> Self {
let mut variants = BTreeMap::new();
variants.insert(alg, digest.into());
Self { variants }
}
#[must_use]
pub fn get(&self, alg: HashAlg) -> Option<&[u8]> {
self.variants.get(&alg).map(AsRef::as_ref)
}
#[must_use]
pub fn contains(&self, alg: HashAlg) -> bool {
self.variants.contains_key(&alg)
}
pub fn algorithms(&self) -> impl Iterator<Item = HashAlg> + '_ {
self.variants.keys().copied()
}
#[must_use]
pub fn len(&self) -> usize {
self.variants.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.variants.is_empty()
}
#[must_use]
pub fn variants(&self) -> &BTreeMap<HashAlg, Box<[u8]>> {
&self.variants
}
#[must_use]
pub fn into_variants(self) -> BTreeMap<HashAlg, Box<[u8]>> {
self.variants
}
pub fn get_or_err(&self, alg: HashAlg) -> crate::error::Result<&[u8]> {
self.get(alg)
.or_else(|| self.variants.values().next().map(AsRef::as_ref))
.ok_or(crate::error::Error::EmptyMultihash)
}
pub fn first_variant(&self) -> crate::error::Result<&[u8]> {
self.variants
.values()
.next()
.map(AsRef::as_ref)
.ok_or(crate::error::Error::EmptyMultihash)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_single_creates_one_variant() {
let digest = vec![0xDE, 0xAD, 0xBE, 0xEF];
let mh = MultihashDigest::from_single(HashAlg::Sha256, digest.clone());
assert_eq!(mh.len(), 1);
assert!(mh.contains(HashAlg::Sha256));
assert!(!mh.contains(HashAlg::Sha384));
assert_eq!(mh.get(HashAlg::Sha256), Some(digest.as_slice()));
}
#[test]
fn new_accepts_multiple_variants() {
let mut variants = BTreeMap::new();
variants.insert(HashAlg::Sha256, vec![0u8; 32].into_boxed_slice());
variants.insert(HashAlg::Sha384, vec![1u8; 48].into_boxed_slice());
let mh = MultihashDigest::new(variants).unwrap();
assert_eq!(mh.len(), 2);
assert!(mh.contains(HashAlg::Sha256));
assert!(mh.contains(HashAlg::Sha384));
assert!(!mh.contains(HashAlg::Sha512));
}
#[test]
fn algorithms_iterates_in_order() {
let mut variants = BTreeMap::new();
variants.insert(HashAlg::Sha512, vec![0u8; 64].into_boxed_slice());
variants.insert(HashAlg::Sha256, vec![0u8; 32].into_boxed_slice());
let mh = MultihashDigest::new(variants).unwrap();
let algs: Vec<_> = mh.algorithms().collect();
assert_eq!(algs.len(), 2);
}
#[test]
fn get_returns_none_for_missing() {
let mh = MultihashDigest::from_single(HashAlg::Sha256, vec![0u8; 32]);
assert!(mh.get(HashAlg::Sha384).is_none());
}
#[test]
fn equality_checks_all_variants() {
let mh1 = MultihashDigest::from_single(HashAlg::Sha256, vec![1, 2, 3]);
let mh2 = MultihashDigest::from_single(HashAlg::Sha256, vec![1, 2, 3]);
let mh3 = MultihashDigest::from_single(HashAlg::Sha256, vec![4, 5, 6]);
assert_eq!(mh1, mh2);
assert_ne!(mh1, mh3);
}
}