1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
11pub struct NodeId(Vec<u8>);
12
13impl NodeId {
14 #[must_use]
16 pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
17 Self(bytes.into())
18 }
19
20 #[must_use]
22 pub fn as_bytes(&self) -> &[u8] {
23 &self.0
24 }
25
26 #[must_use]
28 pub fn is_empty(&self) -> bool {
29 self.0.is_empty()
30 }
31}
32
33impl fmt::Display for NodeId {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 for byte in self.0.iter().take(8) {
37 write!(f, "{byte:02x}")?;
38 }
39 if self.0.len() > 8 {
40 write!(f, "...")?;
41 }
42 Ok(())
43 }
44}
45
46impl AsRef<[u8]> for NodeId {
47 fn as_ref(&self) -> &[u8] {
48 &self.0
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
57pub struct PeerAddr(String);
58
59impl PeerAddr {
60 #[must_use]
62 pub fn new(addr: impl Into<String>) -> Self {
63 Self(addr.into())
64 }
65
66 #[must_use]
68 pub fn as_str(&self) -> &str {
69 &self.0
70 }
71}
72
73impl fmt::Display for PeerAddr {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.write_str(&self.0)
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct WriteReceipt {
82 acknowledged: usize,
83 required: usize,
84}
85
86impl WriteReceipt {
87 #[must_use]
90 pub const fn new(acknowledged: usize, required: usize) -> Self {
91 Self {
92 acknowledged,
93 required,
94 }
95 }
96
97 #[must_use]
99 pub const fn acknowledged(&self) -> usize {
100 self.acknowledged
101 }
102
103 #[must_use]
105 pub const fn required(&self) -> usize {
106 self.required
107 }
108
109 #[must_use]
114 pub const fn exceeded_quorum(&self) -> bool {
115 self.acknowledged > self.required
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn node_id_round_trips_bytes() {
125 let id = NodeId::from_bytes(vec![1, 2, 3]);
126 assert_eq!(id.as_bytes(), &[1, 2, 3]);
127 assert!(!id.is_empty());
128 }
129
130 #[test]
131 fn node_id_display_is_truncated_hex() {
132 let id = NodeId::from_bytes(vec![0xab; 16]);
133 let shown = id.to_string();
134 assert_eq!(shown, "abababababababab...");
135 }
136
137 #[test]
138 fn short_node_id_display_has_no_ellipsis() {
139 let id = NodeId::from_bytes(vec![0xab; 4]);
140 assert_eq!(id.to_string(), "abababab");
141 }
142
143 #[test]
144 fn peer_addr_keeps_its_string_form() {
145 let addr = PeerAddr::new("/ip4/127.0.0.1/tcp/4001");
146 assert_eq!(addr.as_str(), "/ip4/127.0.0.1/tcp/4001");
147 assert_eq!(addr.to_string(), "/ip4/127.0.0.1/tcp/4001");
148 }
149
150 #[test]
151 fn receipt_reports_acknowledgements() {
152 let r = WriteReceipt::new(11, 11);
153 assert_eq!(r.acknowledged(), 11);
154 assert_eq!(r.required(), 11);
155 assert!(!r.exceeded_quorum());
156 }
157
158 #[test]
159 fn receipt_detects_writes_beyond_quorum() {
160 assert!(WriteReceipt::new(20, 11).exceeded_quorum());
161 }
162}