misskey_api/model/id/
aid.rs1use std::convert::TryFrom;
2use std::fmt::{self, Display};
3use std::str::FromStr;
4
5use chrono::{DateTime, TimeZone, Utc};
6use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
7use thiserror::Error;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct Aid {
11 pub timestamp: i64,
12 pub random: u16,
14}
15
16impl Aid {
17 pub fn datetime(&self) -> DateTime<Utc> {
18 Utc.timestamp_millis(self.timestamp)
19 }
20}
21
22const TIME2000: i64 = 946684800000;
24
25#[derive(Debug, Error, Clone)]
26#[error("invalid aid")]
27pub struct ParseAidError {
28 _priv: (),
29}
30
31impl FromStr for Aid {
32 type Err = ParseAidError;
33
34 fn from_str(s: &str) -> Result<Aid, Self::Err> {
35 let (timestamp_str, random_str) = s.split_at(s.len() - 2);
36
37 let timestamp = match i64::from_str_radix(timestamp_str, 36) {
38 Ok(x) => x + TIME2000,
39 Err(_) => return Err(ParseAidError { _priv: () }),
40 };
41
42 let random = match u16::from_str_radix(random_str, 36) {
43 Ok(x) => x,
44 Err(_) => return Err(ParseAidError { _priv: () }),
45 };
46
47 Ok(Aid { timestamp, random })
48 }
49}
50
51struct Radix36(u64);
52
53impl Radix36 {
54 fn new(x: impl Into<u64>) -> Radix36 {
55 Radix36(x.into())
56 }
57}
58
59impl Display for Radix36 {
60 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61 use std::convert::TryInto;
62 use std::fmt::Write;
63
64 let width = f
65 .width()
66 .unwrap_or_else(|| (self.0 as f64).log(36.0).floor() as usize + 1);
67
68 (0..width)
69 .rev()
70 .map(|i| self.0 / 36_u64.pow(i.try_into().unwrap()) % 36)
71 .map(|d| std::char::from_digit(d.try_into().unwrap(), 36).unwrap())
72 .map(|c| f.write_char(c))
73 .collect()
74 }
75}
76
77impl Display for Aid {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 let since_2000 = u64::try_from(self.timestamp - TIME2000).unwrap_or(0);
80 let timestamp_fmt = Radix36::new(since_2000);
81 let random_fmt = Radix36::new(self.random % 1296);
82 write!(f, "{:08}{:02}", timestamp_fmt, random_fmt)
83 }
84}
85
86impl Serialize for Aid {
87 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88 where
89 S: Serializer,
90 {
91 serializer.collect_str(self)
92 }
93}
94
95impl<'de> Deserialize<'de> for Aid {
96 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97 where
98 D: Deserializer<'de>,
99 {
100 String::deserialize(deserializer)?
101 .parse()
102 .map_err(de::Error::custom)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::Aid;
109 use chrono::{DateTime, Duration, TimeZone, Utc};
110 use rand::{self, Rng};
111
112 fn new() -> Aid {
113 from_datetime(Utc::now())
114 }
115
116 fn from_datetime<Tz>(datetime: DateTime<Tz>) -> Aid
117 where
118 Tz: TimeZone,
119 {
120 from_datetime_with_source(datetime, &mut rand::thread_rng())
121 }
122
123 fn from_datetime_with_source<Tz, R>(datetime: DateTime<Tz>, source: &mut R) -> Aid
124 where
125 Tz: TimeZone,
126 R: Rng,
127 {
128 let timestamp = datetime.timestamp_millis();
129 let random = source.gen::<u16>() % 1296;
130 Aid { timestamp, random }
131 }
132
133 #[test]
134 fn test_deserialize_const() {
135 let string = "8dhemt9ubf";
136 let aid: Aid = string.parse().expect("failed to parse");
137 assert_eq!(aid.datetime(), Utc.timestamp_millis(1602948787122));
138 }
139
140 #[test]
141 fn test_serialize_deserialize() {
142 let aid1 = new();
143 let string = aid1.to_string();
144 let aid2: Aid = string.parse().expect("failed to parse");
145 assert_eq!(aid1, aid2);
146 }
147
148 #[test]
149 fn test_deserialize_serialize() {
150 let string1 = "8dhe5zqidm";
151 let aid: Aid = string1.parse().expect("failed to parse");
152 let string2 = aid.to_string();
153 assert_eq!(string1, string2);
154 }
155
156 #[test]
157 fn test_deserialize_serialize2() {
158 let string1 = "8ejiidh50m";
159 let aid: Aid = string1.parse().expect("failed to parse");
160 let string2 = aid.to_string();
161 assert_eq!(string1, string2);
162 }
163
164 #[test]
165 fn test_order() {
166 let time = Utc::now();
167 let aid1 = from_datetime(time);
168 let aid2 = from_datetime(time + Duration::milliseconds(1));
169 assert!(aid1 < aid2);
170 }
171}