robespierre_models/
id.rs

1use std::{
2    cmp::Ordering,
3    convert::{TryFrom, TryInto},
4    fmt::Display,
5    str::FromStr,
6};
7
8use serde::{Deserialize, Serialize};
9
10/// 26-bytes of numeric or uppercase letter characters
11#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
12#[serde(try_from = "String", into = "String")]
13pub struct IdString([u8; 26]);
14
15impl IdString {
16    /// Checks whether the string is a valid Id
17    pub fn check(s: &str) -> Result<(), IdStringDeserializeError> {
18        if s.len() != 26 {
19            return Err(IdStringDeserializeError::IncorrectLength {
20                expected: 26,
21                len: s.len(),
22            });
23        }
24
25        if let Some(pos) = s.find(|c: char| !('A'..='Z').contains(&c) && !('0'..='9').contains(&c))
26        {
27            let c = s.chars().nth(pos).unwrap();
28
29            return Err(IdStringDeserializeError::InvalidCharacter { c, pos });
30        }
31
32        Ok(())
33    }
34
35    /// Creates a new [`IdString`] wihtout checking that
36    /// the contents are valid id characters, and the whole string
37    /// is of the right length.
38    /// # Safety
39    /// s should have passed [Self::check]
40    pub unsafe fn from_str_unchecked(s: &str) -> Self {
41        Self(s.as_bytes().try_into().unwrap())
42    }
43
44    /// Creates a new [`IdString`] wihtout checking that
45    /// the contents are valid id characters, and the whole string
46    /// is of the right length.
47    /// # Safety
48    /// s should have passed [Self::check]
49    pub unsafe fn from_string_unchecked(s: String) -> Self {
50        Self(s.as_bytes().try_into().unwrap())
51    }
52
53    /// Gets the time the object identified by this id was created at.
54    pub fn datetime(&self) -> chrono::DateTime<chrono::Utc> {
55        let ulid = self.as_ref().parse::<rusty_ulid::Ulid>().unwrap();
56
57        ulid.datetime()
58    }
59}
60
61/// An error that can occur while parsing an IdString
62#[derive(thiserror::Error, Debug)]
63pub enum IdStringDeserializeError {
64    #[error("invalid character '{c}' at position {pos}")]
65    InvalidCharacter { pos: usize, c: char },
66    #[error("incorrect length: is {len}, expected {expected}")]
67    IncorrectLength { len: usize, expected: usize },
68}
69
70impl FromStr for IdString {
71    type Err = IdStringDeserializeError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        Self::check(s)?;
75
76        Ok(Self(s.as_bytes().try_into().unwrap()))
77    }
78}
79
80impl TryFrom<String> for IdString {
81    type Error = IdStringDeserializeError;
82
83    fn try_from(s: String) -> Result<Self, Self::Error> {
84        Self::check(&s)?;
85
86        Ok(Self(s.as_bytes().try_into().unwrap()))
87    }
88}
89
90impl AsRef<str> for IdString {
91    fn as_ref(&self) -> &str {
92        unsafe { std::str::from_utf8_unchecked(&self.0) }
93    }
94}
95
96impl From<IdString> for String {
97    fn from(id: IdString) -> Self {
98        id.as_ref().to_string()
99    }
100}
101
102impl std::fmt::Debug for IdString {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        <str as Display>::fmt(self.as_ref(), f)
105    }
106}
107
108impl Display for IdString {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        self.as_ref().fmt(f)
111    }
112}
113
114impl PartialEq<String> for IdString {
115    fn eq(&self, other: &String) -> bool {
116        self.as_ref().eq(other)
117    }
118}
119
120impl PartialEq<str> for IdString {
121    fn eq(&self, other: &str) -> bool {
122        self.as_ref().eq(other)
123    }
124}
125
126impl<'a> PartialEq<&'a str> for IdString {
127    fn eq(&self, other: &&'a str) -> bool {
128        self.as_ref().eq(*other)
129    }
130}
131
132impl PartialOrd<String> for IdString {
133    fn partial_cmp(&self, other: &String) -> Option<Ordering> {
134        Some(self.as_ref().cmp(other))
135    }
136}
137
138impl PartialOrd<str> for IdString {
139    fn partial_cmp(&self, other: &str) -> Option<Ordering> {
140        Some(self.as_ref().cmp(other))
141    }
142}
143
144impl<'a> PartialOrd<&'a str> for IdString {
145    fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
146        Some(self.as_ref().cmp(*other))
147    }
148}
149
150/*
151
152Variable length id strings
153
154*/
155
156/// variable length(up to 26 bytes) of numeric or letter characters
157#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
158#[serde(try_from = "String", into = "String")]
159pub struct VarLenIdString([u8; 26], usize);
160
161impl VarLenIdString {
162    /// Checks whether the string is a valid Id
163    pub fn check(s: &str) -> Result<(), VarLenIdStringDeserializeError> {
164        if s.len() > 26 {
165            return Err(VarLenIdStringDeserializeError::IncorrectLength {
166                expected_most: 26,
167                len: s.len(),
168            });
169        }
170
171        if let Some(pos) = s.find(|c: char| {
172            !('A'..='Z').contains(&c) && !('a'..='z').contains(&c) && !('0'..='9').contains(&c)
173        }) {
174            let c = s.chars().nth(pos).unwrap();
175
176            return Err(VarLenIdStringDeserializeError::InvalidCharacter { c, pos });
177        }
178
179        Ok(())
180    }
181
182    /// Creates a new [`IdString`] wihtout checking that
183    /// the contents are valid id characters, and the whole string
184    /// is of the right length.
185    /// # Safety
186    /// s should have passed [Self::check]
187    pub unsafe fn from_str_unchecked(s: &str) -> Self {
188        let len = s.len();
189        let mut buf = [0; 26];
190        buf[..len].copy_from_slice(s.as_bytes());
191
192        Self(buf, len)
193    }
194
195    /// Creates a new [`IdString`] wihtout checking that
196    /// the contents are valid id characters, and the whole string
197    /// is of the right length.
198    /// # Safety
199    /// s should have passed [Self::check]
200    pub unsafe fn from_string_unchecked(s: String) -> Self {
201        Self::from_str_unchecked(&s)
202    }
203}
204
205/// An error that can occur while parsing an IdString
206#[derive(thiserror::Error, Debug)]
207pub enum VarLenIdStringDeserializeError {
208    #[error("invalid character '{c}' at position {pos}")]
209    InvalidCharacter { pos: usize, c: char },
210    #[error("incorrect length: is {len}, expected at most {expected_most}")]
211    IncorrectLength { len: usize, expected_most: usize },
212}
213
214impl FromStr for VarLenIdString {
215    type Err = VarLenIdStringDeserializeError;
216
217    fn from_str(s: &str) -> Result<Self, Self::Err> {
218        Self::check(s)?;
219
220        Ok(unsafe { Self::from_str_unchecked(s) })
221    }
222}
223
224impl TryFrom<String> for VarLenIdString {
225    type Error = VarLenIdStringDeserializeError;
226
227    fn try_from(s: String) -> Result<Self, Self::Error> {
228        Self::check(&s)?;
229
230        Ok(unsafe { Self::from_string_unchecked(s) })
231    }
232}
233
234impl AsRef<str> for VarLenIdString {
235    fn as_ref(&self) -> &str {
236        unsafe { std::str::from_utf8_unchecked(&self.0[..self.1]) }
237    }
238}
239
240impl From<VarLenIdString> for String {
241    fn from(id: VarLenIdString) -> Self {
242        id.as_ref().to_string()
243    }
244}
245
246impl std::fmt::Debug for VarLenIdString {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        <str as Display>::fmt(self.as_ref(), f)
249    }
250}
251
252impl Display for VarLenIdString {
253    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254        self.as_ref().fmt(f)
255    }
256}
257
258impl PartialEq<String> for VarLenIdString {
259    fn eq(&self, other: &String) -> bool {
260        self.as_ref().eq(other)
261    }
262}
263
264impl PartialEq<str> for VarLenIdString {
265    fn eq(&self, other: &str) -> bool {
266        self.as_ref().eq(other)
267    }
268}
269
270impl<'a> PartialEq<&'a str> for VarLenIdString {
271    fn eq(&self, other: &&'a str) -> bool {
272        self.as_ref().eq(*other)
273    }
274}
275
276impl PartialOrd<String> for VarLenIdString {
277    fn partial_cmp(&self, other: &String) -> Option<Ordering> {
278        Some(self.as_ref().cmp(other))
279    }
280}
281
282impl PartialOrd<str> for VarLenIdString {
283    fn partial_cmp(&self, other: &str) -> Option<Ordering> {
284        Some(self.as_ref().cmp(other))
285    }
286}
287
288impl<'a> PartialOrd<&'a str> for VarLenIdString {
289    fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
290        Some(self.as_ref().cmp(*other))
291    }
292}
293
294macro_rules! id_impl {
295    (@dt $name:ident, $base_ty:ty) => {
296        impl $name {
297            pub fn datetime(&self) -> chrono::DateTime<chrono::Utc> {
298                self.0.datetime()
299            }
300        }
301
302        id_impl!($name, $base_ty);
303    };
304    ($name:ident, $base_ty:ty) => {
305        impl From<$base_ty> for $name {
306            fn from(id: $base_ty) -> Self {
307                Self(id)
308            }
309        }
310
311        impl From<$name> for $base_ty {
312            fn from(id: $name) -> Self {
313                id.0
314            }
315        }
316
317        impl FromStr for $name {
318            type Err = <$base_ty as FromStr>::Err;
319
320            fn from_str(s: &str) -> Result<Self, Self::Err> {
321                s.parse().map(Self)
322            }
323        }
324
325        impl TryFrom<String> for $name {
326            type Error = <$base_ty as TryFrom<String>>::Error;
327
328            fn try_from(value: String) -> Result<Self, Self::Error> {
329                <$base_ty>::try_from(value).map(Self)
330            }
331        }
332
333        impl AsRef<str> for $name {
334            fn as_ref(&self) -> &str {
335                self.0.as_ref()
336            }
337        }
338
339        impl From<$name> for String {
340            fn from(id: $name) -> Self {
341                id.as_ref().to_string()
342            }
343        }
344
345        impl Display for $name {
346            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347                self.0.fmt(f)
348            }
349        }
350
351        impl PartialEq<String> for $name {
352            fn eq(&self, other: &String) -> bool {
353                self.as_ref().eq(other)
354            }
355        }
356
357        impl PartialEq<str> for $name {
358            fn eq(&self, other: &str) -> bool {
359                self.as_ref().eq(other)
360            }
361        }
362
363        impl<'a> PartialEq<&'a str> for $name {
364            fn eq(&self, other: &&'a str) -> bool {
365                self.as_ref().eq(*other)
366            }
367        }
368
369        impl PartialOrd<String> for $name {
370            fn partial_cmp(&self, other: &String) -> Option<Ordering> {
371                Some(self.as_ref().cmp(other))
372            }
373        }
374
375        impl PartialOrd<str> for $name {
376            fn partial_cmp(&self, other: &str) -> Option<Ordering> {
377                Some(self.as_ref().cmp(other))
378            }
379        }
380
381        impl<'a> PartialOrd<&'a str> for $name {
382            fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
383                Some(self.as_ref().cmp(*other))
384            }
385        }
386    };
387}
388
389/// Id type for users.
390#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
391#[serde(transparent)]
392pub struct UserId(IdString);
393
394id_impl! {@dt UserId, IdString}
395
396/// Id type for channels.
397#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
398#[serde(transparent)]
399pub struct ChannelId(IdString);
400
401id_impl! {@dt ChannelId, IdString}
402
403/// Id type for channels.
404#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
405#[serde(transparent)]
406pub struct CategoryId(VarLenIdString);
407
408id_impl! {CategoryId, VarLenIdString}
409
410/// Id type for messages.
411#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
412#[serde(transparent)]
413pub struct MessageId(IdString);
414
415id_impl! {@dt MessageId, IdString}
416
417/// Id type for servers.
418#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
419#[serde(transparent)]
420pub struct ServerId(IdString);
421
422id_impl! {@dt ServerId, IdString}
423
424/// Id type for roles.
425#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
426#[serde(transparent)]
427pub struct RoleId(IdString);
428
429id_impl! {@dt RoleId, IdString}
430
431/// Id type for sessions.
432#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
433#[serde(transparent)]
434pub struct SessionId(IdString);
435
436id_impl! {@dt SessionId, IdString}
437
438/// Id type for invites.
439#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
440#[serde(transparent)]
441pub struct InviteId(IdString);
442
443id_impl! {@dt InviteId, IdString}
444
445/// Id type for members.
446///
447/// Note: it is a pair of a [`ServerId`] and [`UserId`]
448#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
449pub struct MemberId {
450    pub server: ServerId,
451    pub user: UserId,
452}