use std::fmt;
use std::str::FromStr;
use ipld_core::cid::multihash::Multihash;
use ipld_core::cid::Cid;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::error::ContentError;
pub const DAG_CBOR_CODEC: u64 = 0x71;
pub const BLAKE3_HASH_CODE: u64 = 0x1e;
const BLAKE3_DIGEST_LEN: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ContentId(Cid);
impl ContentId {
#[must_use]
pub fn from_canonical_bytes(bytes: &[u8]) -> Self {
let digest = blake3::hash(bytes);
let mh = Multihash::wrap(BLAKE3_HASH_CODE, digest.as_bytes())
.expect("BLAKE3 digest is 32 bytes and always fits a 64-byte multihash");
debug_assert_eq!(digest.as_bytes().len(), BLAKE3_DIGEST_LEN);
ContentId(Cid::new_v1(DAG_CBOR_CODEC, mh))
}
#[must_use]
pub fn as_cid(&self) -> &Cid {
&self.0
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ContentError> {
Cid::read_bytes(bytes)
.map(ContentId)
.map_err(|e| ContentError::InvalidCid {
reason: e.to_string(),
})
}
}
impl From<Cid> for ContentId {
fn from(cid: Cid) -> Self {
ContentId(cid)
}
}
impl From<ContentId> for Cid {
fn from(id: ContentId) -> Self {
id.0
}
}
impl fmt::Display for ContentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for ContentId {
type Err = ContentError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Cid::from_str(s)
.map(ContentId)
.map_err(|e| ContentError::InvalidCid {
reason: e.to_string(),
})
}
}
impl Serialize for ContentId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ContentId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Cid::deserialize(deserializer).map(ContentId)
}
}