1use std::fmt;
8use std::hash::{Hash, Hasher};
9
10use fnv::FnvHasher;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub struct NodeHash {
32 hash: u64,
33 is_wildcard: bool,
34}
35
36impl NodeHash {
37 const WILDCARD_HASH: u64 = 0;
39
40 #[must_use]
44 pub fn from_id(node_id: &str) -> Self {
45 let mut hasher = FnvHasher::default();
46 node_id.hash(&mut hasher);
47 let hash = hasher.finish();
48
49 let hash = if hash == Self::WILDCARD_HASH {
51 hash.wrapping_add(1)
52 } else {
53 hash
54 };
55
56 Self {
57 hash,
58 is_wildcard: false,
59 }
60 }
61
62 #[must_use]
67 pub fn wildcard() -> Self {
68 Self {
69 hash: Self::WILDCARD_HASH,
70 is_wildcard: true,
71 }
72 }
73
74 #[must_use]
76 pub fn is_wildcard(&self) -> bool {
77 self.is_wildcard
78 }
79
80 #[must_use]
82 pub fn as_u64(&self) -> u64 {
83 self.hash
84 }
85}
86
87impl fmt::Display for NodeHash {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 if self.is_wildcard {
90 write!(f, "<wildcard>")
91 } else {
92 write!(f, "{:016x}", self.hash)
93 }
94 }
95}
96
97impl Default for NodeHash {
98 fn default() -> Self {
99 Self::wildcard()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_node_hash_creation() {
109 let node = NodeHash::from_id("test-node");
110 assert!(!node.is_wildcard());
111 assert_ne!(node.as_u64(), 0);
112 }
113
114 #[test]
115 fn test_node_hash_deterministic() {
116 let node1 = NodeHash::from_id("test-node");
117 let node2 = NodeHash::from_id("test-node");
118 assert_eq!(node1, node2);
119 assert_eq!(node1.as_u64(), node2.as_u64());
120 }
121
122 #[test]
123 fn test_different_nodes_different_hashes() {
124 let node1 = NodeHash::from_id("node-1");
125 let node2 = NodeHash::from_id("node-2");
126 assert_ne!(node1, node2);
127 }
128
129 #[test]
130 fn test_wildcard() {
131 let wildcard = NodeHash::wildcard();
132 assert!(wildcard.is_wildcard());
133 assert_eq!(wildcard.as_u64(), 0);
134 }
135
136 #[test]
137 fn test_display() {
138 let node = NodeHash::from_id("test");
139 let display = format!("{node}");
140 assert_eq!(display.len(), 16); let wildcard = NodeHash::wildcard();
143 assert_eq!(format!("{wildcard}"), "<wildcard>");
144 }
145
146 #[test]
147 fn test_default_is_wildcard() {
148 let default = NodeHash::default();
149 assert!(default.is_wildcard());
150 }
151}