1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16pub struct ContentId(String);
17
18impl ContentId {
19 pub fn of(content: &[u8]) -> Self {
21 let hash = blake3::hash(content);
22 Self(hex::encode(hash.as_bytes()))
23 }
24
25 pub fn as_hex(&self) -> &str {
27 &self.0
28 }
29
30 pub fn verify(&self, content: &[u8]) -> bool {
32 Self::of(content) == *self
33 }
34}
35
36impl std::fmt::Display for ContentId {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.write_str(&self.0)
39 }
40}
41
42pub trait ContentAddressable {
44 fn content_id(&self) -> ContentId;
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn test_cid_deterministic() {
54 let a = ContentId::of(b"hello scrybe");
55 let b = ContentId::of(b"hello scrybe");
56 assert_eq!(a, b);
57 }
58
59 #[test]
60 fn test_cid_differs_for_different_content() {
61 let a = ContentId::of(b"foo");
62 let b = ContentId::of(b"bar");
63 assert_ne!(a, b);
64 }
65
66 #[test]
67 fn test_verify_roundtrip() {
68 let content = b"verifiable content";
69 let cid = ContentId::of(content);
70 assert!(cid.verify(content));
71 assert!(!cid.verify(b"different content"));
72 }
73}