oid/
lib.rs

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}