1use std::{
2 cmp::Ordering,
3 convert::{TryFrom, TryInto},
4 fmt::Display,
5 str::FromStr,
6};
7
8use serde::{Deserialize, Serialize};
9
10#[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 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 pub unsafe fn from_str_unchecked(s: &str) -> Self {
41 Self(s.as_bytes().try_into().unwrap())
42 }
43
44 pub unsafe fn from_string_unchecked(s: String) -> Self {
50 Self(s.as_bytes().try_into().unwrap())
51 }
52
53 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#[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#[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 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 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 pub unsafe fn from_string_unchecked(s: String) -> Self {
201 Self::from_str_unchecked(&s)
202 }
203}
204
205#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
449pub struct MemberId {
450 pub server: ServerId,
451 pub user: UserId,
452}