1use core::fmt;
4use core::hash::{Hash, Hasher};
5use std::net::IpAddr;
6
7const INLINE_CAP: usize = 23;
11
12#[derive(Clone)]
13enum Repr {
14 Inline { buf: [u8; INLINE_CAP], len: u8 },
16 Heap(Box<[u8]>),
18}
19
20#[derive(Clone)]
44pub struct Key(Repr);
45
46impl Key {
47 fn from_bytes(bytes: &[u8]) -> Self {
49 if bytes.len() <= INLINE_CAP {
50 let mut buf = [0u8; INLINE_CAP];
51 buf[..bytes.len()].copy_from_slice(bytes);
52 Self(Repr::Inline {
54 buf,
55 len: bytes.len() as u8,
56 })
57 } else {
58 Self(Repr::Heap(bytes.into()))
59 }
60 }
61
62 #[must_use]
73 pub fn as_bytes(&self) -> &[u8] {
74 match &self.0 {
75 Repr::Inline { buf, len } => &buf[..*len as usize],
76 Repr::Heap(bytes) => bytes,
77 }
78 }
79}
80
81impl PartialEq for Key {
82 fn eq(&self, other: &Self) -> bool {
83 self.as_bytes() == other.as_bytes()
84 }
85}
86
87impl Eq for Key {}
88
89impl Hash for Key {
90 fn hash<H: Hasher>(&self, state: &mut H) {
91 self.as_bytes().hash(state);
92 }
93}
94
95impl fmt::Debug for Key {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.debug_tuple("Key").field(&self.as_bytes()).finish()
98 }
99}
100
101impl From<&str> for Key {
102 fn from(value: &str) -> Self {
103 Self::from_bytes(value.as_bytes())
104 }
105}
106
107impl From<String> for Key {
108 fn from(value: String) -> Self {
109 if value.len() <= INLINE_CAP {
110 Self::from_bytes(value.as_bytes())
111 } else {
112 Self(Repr::Heap(value.into_bytes().into_boxed_slice()))
113 }
114 }
115}
116
117impl From<&[u8]> for Key {
118 fn from(value: &[u8]) -> Self {
119 Self::from_bytes(value)
120 }
121}
122
123impl From<Vec<u8>> for Key {
124 fn from(value: Vec<u8>) -> Self {
125 if value.len() <= INLINE_CAP {
126 Self::from_bytes(&value)
127 } else {
128 Self(Repr::Heap(value.into_boxed_slice()))
129 }
130 }
131}
132
133impl From<u64> for Key {
134 fn from(value: u64) -> Self {
135 Self::from_bytes(&value.to_be_bytes())
136 }
137}
138
139impl From<IpAddr> for Key {
140 fn from(value: IpAddr) -> Self {
141 match value {
142 IpAddr::V4(addr) => Self::from_bytes(&addr.octets()),
143 IpAddr::V6(addr) => Self::from_bytes(&addr.octets()),
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::{INLINE_CAP, Key, Repr};
151 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
152
153 fn is_inline(key: &Key) -> bool {
154 matches!(key.0, Repr::Inline { .. })
155 }
156
157 #[test]
158 fn test_str_and_string_are_equal_for_same_bytes() {
159 let a: Key = "user:42".into();
160 let b: Key = String::from("user:42").into();
161 assert_eq!(a, b);
162 assert_eq!(a.as_bytes(), b"user:42");
163 }
164
165 #[test]
166 fn test_distinct_content_is_distinct() {
167 let a: Key = "a".into();
168 let b: Key = "b".into();
169 assert_ne!(a, b);
170 }
171
172 #[test]
173 fn test_byte_slice_and_vec_round_trip() {
174 let bytes: &[u8] = &[1, 2, 3, 4];
175 let a: Key = bytes.into();
176 let b: Key = vec![1u8, 2, 3, 4].into();
177 assert_eq!(a, b);
178 assert_eq!(a.as_bytes(), bytes);
179 }
180
181 #[test]
182 fn test_u64_uses_big_endian_bytes() {
183 let key: Key = 1u64.into();
184 assert_eq!(key.as_bytes(), &[0, 0, 0, 0, 0, 0, 0, 1]);
185 }
186
187 #[test]
188 fn test_ip_addresses_encode_their_octets() {
189 let v4: Key = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)).into();
190 assert_eq!(v4.as_bytes(), &[10, 0, 0, 1]);
191
192 let v6: Key = IpAddr::V6(Ipv6Addr::LOCALHOST).into();
193 assert_eq!(v6.as_bytes().len(), 16);
194 }
195
196 #[test]
197 fn test_small_keys_are_stored_inline() {
198 assert!(is_inline(&Key::from("user:42")));
200 assert!(is_inline(&Key::from(1u64)));
201 assert!(is_inline(&Key::from(IpAddr::V6(Ipv6Addr::LOCALHOST))));
202 let at_cap = vec![b'x'; INLINE_CAP];
204 assert!(is_inline(&Key::from(at_cap.as_slice())));
205 }
206
207 #[test]
208 fn test_large_keys_spill_to_heap_and_compare_equal() {
209 let long = vec![b'y'; INLINE_CAP + 1];
210 let from_slice: Key = long.as_slice().into();
211 let from_vec: Key = long.clone().into();
212 assert!(!is_inline(&from_slice));
213 assert_eq!(from_slice, from_vec);
214 assert_eq!(from_slice.as_bytes(), long.as_slice());
215 }
216
217 #[test]
218 fn test_inline_and_heap_hash_consistently_by_bytes() {
219 use std::collections::hash_map::DefaultHasher;
220 use std::hash::{Hash, Hasher};
221
222 fn hash(key: &Key) -> u64 {
223 let mut h = DefaultHasher::new();
224 key.hash(&mut h);
225 h.finish()
226 }
227
228 let a: Key = "short".into();
231 let b: Key = String::from("short").into();
232 assert_eq!(hash(&a), hash(&b));
233 }
234}