strut_core/replica/
lifetime_id.rs1use std::any::type_name;
2use std::fmt::{Debug, Display, Formatter};
3use std::hash::{DefaultHasher, Hash, Hasher};
4use std::time::SystemTime;
5
6#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct LifetimeId {
10 bytes: [u8; 10],
11}
12
13impl LifetimeId {
14 pub fn random() -> Self {
16 let mut bytes = [0u8; 10];
18
19 let mut hash = 0u64;
21
22 static CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
24
25 for i in 0..bytes.len() {
27 if hash == 0 {
29 hash = Self::make_hash();
30 }
31
32 let idx = (hash % CHARSET.len() as u64) as usize;
34
35 bytes[i] = CHARSET[idx];
37
38 hash >>= 5;
40 }
41
42 Self { bytes }
43 }
44
45 pub fn hyphenated(&self) -> Hyphenated<'_> {
47 Hyphenated(self)
48 }
49
50 pub fn underscored(&self) -> Underscored<'_> {
52 Underscored(self)
53 }
54
55 pub fn dotted(&self) -> Dotted<'_> {
57 Dotted(self)
58 }
59
60 pub fn glued(&self) -> Glued<'_> {
62 Glued(self)
63 }
64}
65
66impl LifetimeId {
67 fn make_hash() -> u64 {
69 let seed = SystemTime::now()
71 .duration_since(SystemTime::UNIX_EPOCH)
72 .expect("current time should always be after UNIX epoch")
73 .as_nanos(); let mut hasher = DefaultHasher::new();
77 seed.hash(&mut hasher);
78
79 hasher.finish()
80 }
81
82 pub fn view_bytes(&self) -> &[u8; 10] {
84 &self.bytes
85 }
86
87 pub fn view_glued(&self) -> &str {
90 std::str::from_utf8(&self.bytes).expect(concat!(
91 "it should be possible to view the internal buffer as a &str because",
92 " the constructor of this struct always interprets the input string",
93 " as a sequence of valid UTF-8 characters",
94 ))
95 }
96
97 pub fn view_chunks(&self) -> (&str, &str, &str) {
100 let chunk_a = std::str::from_utf8(&self.bytes[0..3]).expect(concat!(
101 "it should be possible to view the internal buffer as a &str because",
102 " the constructor of this struct always interprets the input string",
103 " as a sequence of valid UTF-8 characters",
104 ));
105 let chunk_b = std::str::from_utf8(&self.bytes[3..7]).expect(concat!(
106 "it should be possible to view the internal buffer as a &str because",
107 " the constructor of this struct always interprets the input string",
108 " as a sequence of valid UTF-8 characters",
109 ));
110 let chunk_c = std::str::from_utf8(&self.bytes[7..10]).expect(concat!(
111 "it should be possible to view the internal buffer as a &str because",
112 " the constructor of this struct always interprets the input string",
113 " as a sequence of valid UTF-8 characters",
114 ));
115
116 (chunk_a, chunk_b, chunk_c)
117 }
118}
119
120impl Display for LifetimeId {
121 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122 Display::fmt(&self.hyphenated(), f)
123 }
124}
125
126impl Debug for LifetimeId {
127 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
128 f.debug_struct(type_name::<Self>())
129 .field("bytes", &self.hyphenated())
130 .finish()
131 }
132}
133
134pub struct Hyphenated<'a>(&'a LifetimeId);
138
139impl<'a> Display for Hyphenated<'a> {
140 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
141 let (chunk_a, chunk_b, chunk_c) = self.0.view_chunks();
142
143 write!(f, "{}-{}-{}", chunk_a, chunk_b, chunk_c)
144 }
145}
146
147impl<'a> Debug for Hyphenated<'a> {
148 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149 Display::fmt(self, f)
150 }
151}
152
153pub struct Underscored<'a>(&'a LifetimeId);
157
158impl<'a> Display for Underscored<'a> {
159 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
160 let (chunk_a, chunk_b, chunk_c) = self.0.view_chunks();
161
162 write!(f, "{}_{}_{}", chunk_a, chunk_b, chunk_c)
163 }
164}
165
166impl<'a> Debug for Underscored<'a> {
167 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
168 Display::fmt(self, f)
169 }
170}
171
172pub struct Dotted<'a>(&'a LifetimeId);
176
177impl<'a> Display for Dotted<'a> {
178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179 let (chunk_a, chunk_b, chunk_c) = self.0.view_chunks();
180
181 write!(f, "{}.{}.{}", chunk_a, chunk_b, chunk_c)
182 }
183}
184
185impl<'a> Debug for Dotted<'a> {
186 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187 Display::fmt(self, f)
188 }
189}
190
191pub struct Glued<'a>(&'a LifetimeId);
194
195impl<'a> Display for Glued<'a> {
196 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
197 write!(f, "{}", self.0.view_glued())
198 }
199}
200
201impl<'a> Debug for Glued<'a> {
202 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203 Display::fmt(self, f)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use pretty_assertions::{assert_eq, assert_ne};
210
211 #[test]
212 fn generate_lifetime_id() {
213 let lifetime_id = super::LifetimeId::random().to_string();
215
216 assert_eq!(lifetime_id.len(), 12);
218 assert!(
219 lifetime_id
220 .chars()
221 .all(|c| c.is_ascii_alphabetic() || c == '-')
222 );
223 assert!(
224 lifetime_id
225 .chars()
226 .all(|c| c.is_ascii_lowercase() || c == '-')
227 );
228 assert!(
229 matches!(lifetime_id.chars().nth(0), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
230 );
231 assert!(
232 matches!(lifetime_id.chars().nth(1), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
233 );
234 assert!(
235 matches!(lifetime_id.chars().nth(2), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
236 );
237 assert!(matches!(lifetime_id.chars().nth(3), Some(c) if c == '-'));
238 assert!(
239 matches!(lifetime_id.chars().nth(4), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
240 );
241 assert!(
242 matches!(lifetime_id.chars().nth(5), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
243 );
244 assert!(
245 matches!(lifetime_id.chars().nth(6), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
246 );
247 assert!(
248 matches!(lifetime_id.chars().nth(7), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
249 );
250 assert!(matches!(lifetime_id.chars().nth(8), Some(c) if c == '-'));
251 assert!(
252 matches!(lifetime_id.chars().nth(9), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
253 );
254 assert!(
255 matches!(lifetime_id.chars().nth(10), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
256 );
257 assert!(
258 matches!(lifetime_id.chars().nth(11), Some(c) if c.is_ascii_lowercase() && c.is_ascii_lowercase())
259 );
260 }
261
262 #[test]
263 fn generate_lifetime_ids() {
264 let lifetime_id_a = super::LifetimeId::random().to_string();
266 let lifetime_id_b = super::LifetimeId::random().to_string();
267
268 assert_ne!(lifetime_id_a, lifetime_id_b);
270 }
271}