misskey_api/model/id/
meid.rs

1use std::fmt::{self, Display};
2use std::str::FromStr;
3
4use chrono::{DateTime, TimeZone, Utc};
5use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
6use thiserror::Error;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct Meid {
10    pub timestamp: i64,
11    /// 0 - 2^48
12    pub random: u64,
13}
14
15impl Meid {
16    pub fn datetime(&self) -> DateTime<Utc> {
17        Utc.timestamp_millis(self.timestamp)
18    }
19}
20
21// https://github.com/syuilo/misskey/blob/develop/src/misc/id/meid.ts#L9
22const TIME_OFFSET: i64 = 0x800000000000;
23
24#[derive(Debug, Error, Clone)]
25#[error("invalid meid")]
26pub struct ParseMeidError {
27    _priv: (),
28}
29
30impl FromStr for Meid {
31    type Err = ParseMeidError;
32
33    fn from_str(s: &str) -> Result<Meid, Self::Err> {
34        let (timestamp_str, random_str) = s.split_at(s.len() - 12);
35
36        let timestamp = match i64::from_str_radix(timestamp_str, 16) {
37            Ok(0) => 0,
38            Ok(x) => x - TIME_OFFSET,
39            Err(_) => return Err(ParseMeidError { _priv: () }),
40        };
41
42        let random = match u64::from_str_radix(random_str, 16) {
43            Ok(x) => x,
44            Err(_) => return Err(ParseMeidError { _priv: () }),
45        };
46
47        Ok(Meid { timestamp, random })
48    }
49}
50
51impl Display for Meid {
52    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53        let with_offset = if self.timestamp <= 0 {
54            0
55        } else {
56            self.timestamp + TIME_OFFSET
57        };
58        write!(
59            f,
60            "{:012x}{:012x}",
61            with_offset,
62            self.random % 2_u64.pow(48)
63        )
64    }
65}
66
67impl Serialize for Meid {
68    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: Serializer,
71    {
72        serializer.collect_str(self)
73    }
74}
75
76impl<'de> Deserialize<'de> for Meid {
77    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78    where
79        D: Deserializer<'de>,
80    {
81        String::deserialize(deserializer)?
82            .parse()
83            .map_err(de::Error::custom)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::Meid;
90    use chrono::{DateTime, Duration, TimeZone, Utc};
91    use rand::{self, Rng};
92
93    fn new() -> Meid {
94        from_datetime(Utc::now())
95    }
96
97    fn from_datetime<Tz>(datetime: DateTime<Tz>) -> Meid
98    where
99        Tz: TimeZone,
100    {
101        from_datetime_with_source(datetime, &mut rand::thread_rng())
102    }
103
104    fn from_datetime_with_source<Tz, R>(datetime: DateTime<Tz>, source: &mut R) -> Meid
105    where
106        Tz: TimeZone,
107        R: Rng,
108    {
109        let timestamp = datetime.timestamp_millis();
110        let random = source.gen::<u64>() % 2_u64.pow(48);
111        Meid { timestamp, random }
112    }
113
114    #[test]
115    fn test_deserialize_const() {
116        let string = "817537316bb2ef661de6af11";
117        let meid: Meid = string.parse().expect("failed to parse");
118        assert_eq!(meid.datetime(), Utc.timestamp_millis(1602948787122));
119    }
120
121    #[test]
122    fn test_serialize_deserialize() {
123        let meid1 = new();
124        let string = meid1.to_string();
125        let meid2: Meid = string.parse().expect("failed to parse");
126        assert_eq!(meid1, meid2);
127    }
128
129    #[test]
130    fn test_deserialize_serialize() {
131        let string1 = "817537316bb2ef661de6af11";
132        let meid: Meid = string1.parse().expect("failed to parse");
133        let string2 = meid.to_string();
134        assert_eq!(string1, string2);
135    }
136
137    #[test]
138    fn test_order() {
139        let time = Utc::now();
140        let meid1 = from_datetime(time);
141        let meid2 = from_datetime(time + Duration::milliseconds(1));
142        assert!(meid1 < meid2);
143    }
144}