titanium_model/
snowflake.rs

1//! Snowflake ID type for Discord
2//!
3//! Discord uses 64-bit unsigned integers for unique identifiers,
4//! but serializes them as strings in JSON to avoid precision loss.
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::fmt;
8
9/// A Discord Snowflake ID.
10///
11/// Snowflakes are unique 64-bit unsigned integers used by Discord.
12/// They are serialized as strings in JSON to prevent precision loss
13/// in languages with limited integer precision (JavaScript).
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
15pub struct Snowflake(pub u64);
16
17impl Snowflake {
18    /// Create a new Snowflake from a u64 value.
19    #[inline]
20    pub const fn new(id: u64) -> Self {
21        Self(id)
22    }
23
24    /// Get the raw u64 value.
25    #[inline]
26    pub const fn get(self) -> u64 {
27        self.0
28    }
29
30    /// Extract the timestamp from this Snowflake.
31    ///
32    /// Returns milliseconds since Discord Epoch (2015-01-01T00:00:00Z).
33    #[inline]
34    pub const fn timestamp(self) -> u64 {
35        (self.0 >> 22) + 1420070400000
36    }
37
38    /// Extract the internal worker ID.
39    #[inline]
40    pub const fn worker_id(self) -> u8 {
41        ((self.0 & 0x3E0000) >> 17) as u8
42    }
43
44    /// Extract the internal process ID.
45    #[inline]
46    pub const fn process_id(self) -> u8 {
47        ((self.0 & 0x1F000) >> 12) as u8
48    }
49
50    /// Extract the increment (sequence number within the same millisecond).
51    #[inline]
52    pub const fn increment(self) -> u16 {
53        (self.0 & 0xFFF) as u16
54    }
55}
56
57impl fmt::Display for Snowflake {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}", self.0)
60    }
61}
62
63impl From<u64> for Snowflake {
64    #[inline]
65    fn from(value: u64) -> Self {
66        Self(value)
67    }
68}
69
70impl From<Snowflake> for u64 {
71    #[inline]
72    fn from(snowflake: Snowflake) -> Self {
73        snowflake.0
74    }
75}
76
77impl Serialize for Snowflake {
78    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
79    where
80        S: Serializer,
81    {
82        // Always serialize as string to match Discord's format
83        serializer.serialize_str(&self.0.to_string())
84    }
85}
86
87impl<'de> Deserialize<'de> for Snowflake {
88    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89    where
90        D: Deserializer<'de>,
91    {
92        // Discord sends snowflakes as strings, but we also handle integers
93        struct SnowflakeVisitor;
94
95        impl<'de> serde::de::Visitor<'de> for SnowflakeVisitor {
96            type Value = Snowflake;
97
98            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
99                formatter.write_str("a string or integer snowflake ID")
100            }
101
102            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
103            where
104                E: serde::de::Error,
105            {
106                Ok(Snowflake(value))
107            }
108
109            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
110            where
111                E: serde::de::Error,
112            {
113                Ok(Snowflake(value as u64))
114            }
115
116            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
117            where
118                E: serde::de::Error,
119            {
120                value
121                    .parse::<u64>()
122                    .map(Snowflake)
123                    .map_err(serde::de::Error::custom)
124            }
125        }
126
127        deserializer.deserialize_any(SnowflakeVisitor)
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_snowflake_parsing() {
137        let json_str = r#""175928847299117063""#;
138        let snowflake: Snowflake = crate::json::from_str(json_str).unwrap();
139        assert_eq!(snowflake.get(), 175928847299117063);
140    }
141
142    #[test]
143    fn test_snowflake_serialization() {
144        let snowflake = Snowflake::new(175928847299117063);
145        let json = crate::json::to_string(&snowflake).unwrap();
146        assert_eq!(json, r#""175928847299117063""#);
147    }
148
149    #[test]
150    fn test_snowflake_timestamp() {
151        // Known snowflake with known timestamp
152        let snowflake = Snowflake::new(175928847299117063);
153        assert!(snowflake.timestamp() > 1420070400000);
154    }
155}