1use crate::{
2 Error, Result, Snowflake, SnowflakeDiscordId, SnowflakeInstagramId, SnowflakeMastodonId,
3 SnowflakeTwitterId,
4};
5use base32::{decode, encode, Alphabet};
6use std::convert::TryInto;
7
8const U32_SIZE: usize = std::mem::size_of::<u32>();
9const U64_SIZE: usize = std::mem::size_of::<u64>();
10const U128_SIZE: usize = std::mem::size_of::<u128>();
11
12pub trait BeBytes: Sized {
14 type ByteArray: AsRef<[u8]>;
15
16 fn to_be_bytes(self) -> Self::ByteArray;
17
18 fn from_be_bytes(bytes: &[u8]) -> Option<Self>;
19}
20
21impl BeBytes for u32 {
22 type ByteArray = [u8; U32_SIZE];
23
24 fn to_be_bytes(self) -> Self::ByteArray {
25 self.to_be_bytes()
26 }
27
28 fn from_be_bytes(bytes: &[u8]) -> Option<Self> {
29 let arr: [u8; U32_SIZE] = bytes.try_into().ok()?;
30 Some(Self::from_be_bytes(arr))
31 }
32}
33
34impl BeBytes for u64 {
35 type ByteArray = [u8; U64_SIZE];
36
37 fn to_be_bytes(self) -> Self::ByteArray {
38 self.to_be_bytes()
39 }
40
41 fn from_be_bytes(bytes: &[u8]) -> Option<Self> {
42 let arr: [u8; U64_SIZE] = bytes.try_into().ok()?;
43 Some(Self::from_be_bytes(arr))
44 }
45}
46
47impl BeBytes for u128 {
48 type ByteArray = [u8; U128_SIZE];
49
50 fn to_be_bytes(self) -> Self::ByteArray {
51 self.to_be_bytes()
52 }
53
54 fn from_be_bytes(bytes: &[u8]) -> Option<Self> {
55 let arr: [u8; U128_SIZE] = bytes.try_into().ok()?;
56 Some(Self::from_be_bytes(arr))
57 }
58}
59
60pub trait Base32: Snowflake + Sized
62where
63 Self::Ty: BeBytes,
64{
65 fn encode(&self) -> String {
66 let bytes = self.to_raw().to_be_bytes();
67 encode(Alphabet::Crockford, bytes.as_ref())
68 }
69
70 fn decode(s: &str) -> Result<Self> {
71 let bytes = decode(Alphabet::Crockford, s).ok_or(Error::DecodeNonAsciiValue)?;
72 let raw = Self::Ty::from_be_bytes(&bytes).ok_or(Error::DecodeInvalidLen)?;
73 Ok(Self::from_raw(raw))
74 }
75}
76
77impl Base32 for SnowflakeTwitterId {}
78impl Base32 for SnowflakeDiscordId {}
79impl Base32 for SnowflakeInstagramId {}
80impl Base32 for SnowflakeMastodonId {}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use core::any::type_name;
86 use core::fmt;
87
88 fn test_encode_decode<T>(id: T, label: &str)
89 where
90 T: Snowflake + Base32 + PartialEq + fmt::Debug,
91 T::Ty: BeBytes + fmt::Binary + fmt::LowerHex + fmt::Display + fmt::Debug,
92 {
93 let raw = id.to_raw();
94 let encoded = id.encode();
95 let decoded = T::decode(&encoded).expect("decode should succeed");
96
97 let type_name = type_name::<T>();
98
99 println!("=== {} {} ===", type_name, label);
100 println!("id (raw decimal): {}", raw);
101 println!("id (raw binary): {:064b}", raw);
102 println!("timestamp: 0x{:x}", id.timestamp());
103 println!("machine id: 0x{:x}", id.machine_id());
104 println!("sequence: 0x{:x}", id.sequence());
105 println!("encoded: {}", encoded);
106 println!("decoded: {:?}", decoded);
107
108 assert_eq!(id, decoded, "{} roundtrip failed for {}", label, type_name);
109 }
110
111 #[test]
112 fn twitter_max() {
113 let id = SnowflakeTwitterId::from_components(
114 SnowflakeTwitterId::max_timestamp(),
115 SnowflakeTwitterId::max_machine_id(),
116 SnowflakeTwitterId::max_sequence(),
117 );
118 test_encode_decode(id, "max");
119 assert_eq!(id.to_raw(), 9_223_372_036_854_775_807) }
121
122 #[test]
123 fn twitter_zero() {
124 let id = SnowflakeTwitterId::from_components(
125 SnowflakeTwitterId::ZERO,
126 SnowflakeTwitterId::ZERO,
127 SnowflakeTwitterId::ZERO,
128 );
129 test_encode_decode(id, "zero");
130 assert_eq!(id.to_raw(), 0)
131 }
132
133 #[test]
134 fn discord_max() {
135 let id = SnowflakeDiscordId::from_components(
136 SnowflakeDiscordId::max_timestamp(),
137 SnowflakeDiscordId::max_machine_id(),
138 SnowflakeDiscordId::max_sequence(),
139 );
140 test_encode_decode(id, "max");
141 assert_eq!(id.to_raw(), 18_446_744_073_709_551_615)
142 }
143
144 #[test]
145 fn discord_zero() {
146 let id = SnowflakeDiscordId::from_components(
147 SnowflakeDiscordId::ZERO,
148 SnowflakeDiscordId::ZERO,
149 SnowflakeDiscordId::ZERO,
150 );
151 test_encode_decode(id, "zero");
152 assert_eq!(id.to_raw(), 0)
153 }
154
155 #[test]
156 fn instagram_max() {
157 let id = SnowflakeInstagramId::from_components(
158 SnowflakeInstagramId::max_timestamp(),
159 SnowflakeInstagramId::max_machine_id(),
160 SnowflakeInstagramId::max_sequence(),
161 );
162 test_encode_decode(id, "max");
163 assert_eq!(id.to_raw(), 18_446_744_073_709_551_615)
164 }
165
166 #[test]
167 fn instagram_zero() {
168 let id = SnowflakeInstagramId::from_components(
169 SnowflakeInstagramId::ZERO,
170 SnowflakeInstagramId::ZERO,
171 SnowflakeInstagramId::ZERO,
172 );
173 test_encode_decode(id, "zero");
174 assert_eq!(id.to_raw(), 0)
175 }
176
177 #[test]
178 fn mastodon_max() {
179 let id = SnowflakeMastodonId::from_components(
180 SnowflakeMastodonId::max_timestamp(),
181 SnowflakeMastodonId::max_machine_id(),
182 SnowflakeMastodonId::max_sequence(),
183 );
184 test_encode_decode(id, "max");
185 assert_eq!(id.to_raw(), 18_446_744_073_709_551_615)
186 }
187
188 #[test]
189 fn mastodon_zero() {
190 let id = SnowflakeMastodonId::from_components(
191 SnowflakeMastodonId::ZERO,
192 SnowflakeMastodonId::ZERO,
193 SnowflakeMastodonId::ZERO,
194 );
195 test_encode_decode(id, "zero");
196 assert_eq!(id.to_raw(), 0)
197 }
198
199 #[test]
200 fn decode_invalid_character_fails() {
201 let invalid = "01234@6789ABCDEF";
203 let result = SnowflakeTwitterId::decode(invalid);
204 assert!(matches!(result, Err(Error::DecodeNonAsciiValue)));
205 }
206
207 #[test]
208 fn decode_invalid_length_fails() {
209 let too_short = "ABC";
211 let result = SnowflakeTwitterId::decode(too_short);
212 assert!(matches!(result, Err(Error::DecodeInvalidLen)));
213 }
214}