cortexkit_bus_trait/
message.rs1use std::collections::BTreeMap;
2use std::fmt;
3use std::str::FromStr;
4
5use sha2::{Digest, Sha256};
6
7pub type Headers = BTreeMap<String, String>;
8pub type MessageId = String;
9
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct ContentDigest([u8; 32]);
13
14impl ContentDigest {
15 pub fn of_bytes(bytes: &[u8]) -> Self {
16 Self(Sha256::digest(bytes).into())
17 }
18
19 pub fn as_bytes(&self) -> &[u8; 32] {
20 &self.0
21 }
22}
23
24impl fmt::Debug for ContentDigest {
25 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26 fmt::Display::fmt(self, formatter)
27 }
28}
29
30impl fmt::Display for ContentDigest {
31 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32 for byte in self.0 {
33 write!(formatter, "{byte:02x}")?;
34 }
35 Ok(())
36 }
37}
38
39impl FromStr for ContentDigest {
40 type Err = &'static str;
41
42 fn from_str(value: &str) -> Result<Self, Self::Err> {
43 if value.len() != 64 {
44 return Err("SHA-256 digest must contain exactly 64 hexadecimal characters");
45 }
46 if value
47 .bytes()
48 .any(|byte| !byte.is_ascii_digit() && !(b'a'..=b'f').contains(&byte))
49 {
50 return Err("SHA-256 digest must use lowercase hexadecimal");
51 }
52
53 let mut digest = [0_u8; 32];
54 for (index, byte) in digest.iter_mut().enumerate() {
55 let offset = index * 2;
56 let pair = &value[offset..offset + 2];
57 *byte = u8::from_str_radix(pair, 16).expect("hexadecimal was checked above");
58 }
59 Ok(Self(digest))
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct Message {
65 pub subject: String,
66 pub id: MessageId,
67 pub digest: ContentDigest,
68 pub headers: Headers,
69}
70
71impl Message {
72 pub fn wire_size(&self) -> usize {
73 self.subject.len()
74 + self.id.len()
75 + 64
76 + self
77 .headers
78 .iter()
79 .map(|(name, value)| name.len() + value.len())
80 .sum::<usize>()
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn digest_wire_form_is_lowercase_sha256() {
90 let digest = ContentDigest::of_bytes(b"record");
91 let encoded = digest.to_string();
92
93 assert_eq!(encoded.len(), 64);
94 assert_eq!(encoded.parse::<ContentDigest>(), Ok(digest));
95 assert!(encoded.to_uppercase().parse::<ContentDigest>().is_err());
96 }
97}