1use std::fmt::{Debug, Display};
2
3static ALPHABET: &str = "0abcdefghjkmnpqrstuvwxyz23456789";
4
5const SHORT_LENGTH: usize = 22;
6
7#[derive(Clone)]
8pub struct Test<T> {
9 _marker: T,
10}
11
12fn base32_encode(data: &[u8], alphabet: &str) -> String {
13 let mut result = String::new();
14 let mut buffer = 0u64;
15 let mut bits_in_buffer = 0usize;
16
17 let alphabet_chars: Vec<char> = alphabet.chars().collect();
18
19 for byte in data {
20 buffer = (buffer << 8) | (*byte as u64);
21 bits_in_buffer += 8;
22
23 while bits_in_buffer >= 5 {
24 bits_in_buffer -= 5;
25 let index = ((buffer >> bits_in_buffer) & 0x1F) as usize;
26 result.push(alphabet_chars[index]);
27 }
28 }
29
30 if bits_in_buffer > 0 {
31 let index = ((buffer << (5 - bits_in_buffer)) & 0x1F) as usize;
32 result.push(alphabet_chars[index]);
33 }
34
35 result
36}
37
38#[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
39fn unix_epoch_ms() -> u64 {
40 #[cfg(feature = "time")]
41 {
42 let now = time::OffsetDateTime::now_utc();
43
44 now.unix_timestamp() as u64 * 1_000 + now.millisecond() as u64
45 }
46 #[cfg(all(feature = "chrono", not(feature = "time")))]
47 {
48 let now: chrono::DateTime<chrono::Utc> = chrono::Utc::now();
49
50 now.timestamp_millis() as u64
51 }
52}
53
54pub struct NoLabel;
55
56#[derive(PartialOrd, PartialEq, Eq, Ord, Hash, Copy)]
57pub struct Oid<T = NoLabel> {
58 data: [u8; 16],
59 marker: std::marker::PhantomData<T>,
60}
61
62impl<T> Clone for Oid<T> {
63 fn clone(&self) -> Self {
64 Self {
65 data: self.data,
66 marker: Default::default(),
67 }
68 }
69}
70
71impl Label for NoLabel {
72 fn label() -> &'static str {
73 ""
74 }
75}
76
77pub fn new_oid() -> Oid {
78 NoLabel::oid()
79}
80
81impl<T: Label> Oid<T> {
82 #[cfg(feature = "rand")]
83 pub fn from_timestamp_with_rng<R>(timestamp: u64, rng: &mut R) -> Self
84 where
85 R: rand::Rng,
86 {
87 if (timestamp & 0xFFFF_0000_0000_0000) != 0 {
88 panic!("oid does not support timestamps after +10889-08-02T05:31:50.655Z");
89 }
90 let high = timestamp << 16 | u64::from(rng.gen::<u16>());
91 let low = rng.gen::<u64>();
92 let high = high.to_le_bytes();
93 let low = low.to_le_bytes();
94
95 let mut data: [u8; 16] = [0; 16];
96 data[..8].copy_from_slice(&high);
97 data[8..].copy_from_slice(&low);
98
99 Self {
100 data,
101 marker: Default::default(),
102 }
103 }
104
105 pub fn short(&self) -> String {
106 let encoded = base32_encode(&self.data, ALPHABET);
107 format!("{}{}", T::label(), &encoded[SHORT_LENGTH..])
108 }
109
110 #[cfg(feature = "uuid")]
111 pub fn uuid(&self) -> uuid::Uuid {
112 uuid::Uuid::from_bytes(self.data)
113 }
114}
115
116#[cfg(feature = "uuid")]
117impl<T> Into<uuid::Uuid> for Oid<T> {
118 fn into(self) -> uuid::Uuid {
119 uuid::Uuid::from_bytes(self.data)
120 }
121}
122
123#[cfg(feature = "uuid")]
124impl<T: Label> From<uuid::Uuid> for Oid<T> {
125 fn from(value: uuid::Uuid) -> Self {
126 let bytes = value.as_ref();
127 let mut data: [u8; 16] = [0; 16];
128 data.copy_from_slice(bytes);
129 T::new(data)
130 }
131}
132
133impl<T: Label> Debug for Oid<T> {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 let encoded = base32_encode(&self.data, ALPHABET);
136 write!(f, "{}{}_{}",
137 T::label(),
138 &encoded[..SHORT_LENGTH],
139 &encoded[SHORT_LENGTH..],
140 )
141 }
142}
143
144pub trait Label {
145 fn label() -> &'static str;
146
147 #[cfg(all(feature = "rand", any(feature = "chrono", feature = "time")))]
148 fn oid() -> Oid<Self> where Self: Sized {
149 Oid::from_timestamp_with_rng(unix_epoch_ms(), &mut rand::thread_rng())
150 }
151
152 fn new(bytes: [u8; 16]) -> Oid<Self> where Self: Sized {
153 Oid {
154 data: bytes,
155 marker: Default::default(),
156 }
157 }
158
159 fn null() -> Oid<Self> where Self: Sized {
160 Oid {
161 data: [0; 16],
162 marker: Default::default(),
163 }
164 }
165}
166
167macro_rules! label {
168 ($name:ident, $label:literal) => {
169 pub struct $name;
170 impl Label for $name {
171 fn label() -> &'static str {
172 concat!($label, "_")
173 }
174 }
175 };
176}
177
178impl<T: Label> Display for Oid<T> {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 write!(f, "{:?}", self)
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 label!(Team, "team");
189
190 #[test]
191 fn it_works() {
192 let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
193 let oid = Team::new(bytes);
194 println!("{}", oid.short());
195 println!("{}", oid);
196 assert_eq!(oid.to_string(), "team_0da0fa0e02cssbhkanf04c_srb0");
197 assert_eq!(oid.short(), "team_srb0");
198 }
199
200 #[test]
201 fn test_null() {
202 let oid = Team::null();
203 println!("{}", oid.short());
204 println!("{}", oid);
205 assert_eq!(oid.to_string(), "team_0000000000000000000000_0000");
206 assert_eq!(oid.short(), "team_0000");
207 let oid = NoLabel::null();
208 assert_eq!(oid.to_string(), "0000000000000000000000_0000");
209 }
210
211 #[test]
212 #[cfg(feature = "uuid")]
213 fn test_uuid() {
214 let bytes = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
215 let oid = Team::new(bytes);
216 let uuid: uuid::Uuid = oid.clone().into();
217 assert_eq!(uuid.to_string(), "01020304-0506-0708-090a-0b0c0d0e0f10");
218 let uuid2 = oid.uuid();
219 assert_eq!(uuid, uuid2);
220 }
221}