ferroid/
id.rs

1use crate::{Error, Result};
2use core::fmt;
3use std::{
4    hash::Hash,
5    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
6};
7
8/// Trait for converting numeric-like values into a `u64`.
9///
10/// This is typically used to normalize custom duration types into milliseconds
11/// for compatibility with APIs like [`std::time::Duration::from_millis`], which
12/// are commonly required in async sleep contexts such as
13/// [`tokio::time::sleep`].
14pub trait ToU64 {
15    fn to_u64(self) -> Result<u64>;
16}
17
18impl ToU64 for u8 {
19    fn to_u64(self) -> Result<u64> {
20        Ok(self as u64)
21    }
22}
23
24impl ToU64 for u16 {
25    fn to_u64(self) -> Result<u64> {
26        Ok(self as u64)
27    }
28}
29
30impl ToU64 for u32 {
31    fn to_u64(self) -> Result<u64> {
32        Ok(self as u64)
33    }
34}
35
36impl ToU64 for u64 {
37    fn to_u64(self) -> Result<u64> {
38        Ok(self)
39    }
40}
41
42impl ToU64 for u128 {
43    fn to_u64(self) -> Result<u64> {
44        self.try_into().map_err(|_| Error::FailedToU64)
45    }
46}
47
48/// A trait representing a layout-compatible Snowflake ID generator.
49///
50/// This trait abstracts the core behavior of a Snowflake-style ID with separate
51/// bit fields for timestamp, machine ID, and sequence.
52///
53/// Types implementing this trait can define custom bit layouts and time units.
54///
55/// # Example
56///
57/// ```
58/// use ferroid::{Snowflake, SnowflakeTwitterId};
59///
60/// let id = SnowflakeTwitterId::from(1000, 2, 1);
61/// assert_eq!(id.timestamp(), 1000);
62/// assert_eq!(id.machine_id(), 2);
63/// assert_eq!(id.sequence(), 1);
64/// ```
65pub trait Snowflake:
66    Sized + Copy + Clone + fmt::Display + PartialOrd + Ord + PartialEq + Eq + Hash
67{
68    /// Scalar type for all bit fields (typically `u64`)
69    type Ty: Copy
70        + Clone
71        + Add<Output = Self::Ty>
72        + AddAssign
73        + Sub<Output = Self::Ty>
74        + SubAssign
75        + Mul<Output = Self::Ty>
76        + MulAssign
77        + Div<Output = Self::Ty>
78        + DivAssign
79        + Into<Self::Ty>
80        + From<Self::Ty>
81        + Ord
82        + PartialOrd
83        + Eq
84        + PartialEq
85        + Hash
86        + ToU64
87        + fmt::Debug
88        + fmt::Display;
89
90    /// Zero value (used for resetting the sequence)
91    const ZERO: Self::Ty;
92
93    /// One value (used for incrementing the sequence)
94    const ONE: Self::Ty;
95
96    /// Returns the timestamp portion of the ID.
97    fn timestamp(&self) -> Self::Ty;
98
99    /// Returns the maximum possible value for the timestamp field.
100    fn max_timestamp() -> Self::Ty;
101
102    /// Returns the machine ID portion of the ID.
103    fn machine_id(&self) -> Self::Ty;
104
105    /// Returns the maximum possible value for the machine_id field.
106    fn max_machine_id() -> Self::Ty;
107
108    /// Returns the sequence portion of the ID.
109    fn sequence(&self) -> Self::Ty;
110
111    /// Returns the maximum possible value for the sequence field.
112    fn max_sequence() -> Self::Ty;
113
114    /// Constructs a new Snowflake ID from its components.
115    fn from_components(timestamp: Self::Ty, machine_id: Self::Ty, sequence: Self::Ty) -> Self;
116
117    /// Converts this type into its raw type representation
118    fn to_raw(&self) -> Self::Ty;
119
120    /// Converts a raw type into this type
121    fn from_raw(raw: Self::Ty) -> Self;
122
123    /// Returns true if the current sequence value can be incremented.
124    fn has_sequence_room(&self) -> bool {
125        self.sequence() < Self::max_sequence()
126    }
127
128    /// Returns the next sequence value.
129    fn next_sequence(&self) -> Self::Ty {
130        self.sequence() + Self::ONE
131    }
132
133    /// Returns a new ID with the sequence incremented.
134    fn increment_sequence(&self) -> Self {
135        Self::from_components(self.timestamp(), self.machine_id(), self.next_sequence())
136    }
137
138    /// Returns a new ID for a newer timestamp with sequence reset to zero.
139    fn rollover_to_timestamp(&self, ts: Self::Ty) -> Self {
140        Self::from_components(ts, self.machine_id(), Self::ZERO)
141    }
142
143    fn to_padded_string(&self) -> String;
144}
145
146/// # Field Ordering Semantics
147///
148/// The `define_snowflake_id!` macro defines a bit layout for a custom Snowflake
149/// ID using four required components: `reserved`, `timestamp`, `machine_id`,
150/// and `sequence`.
151///
152/// These components are always laid out from **most significant bit (MSB)** to
153/// **least significant bit (LSB)** - in that exact order.
154///
155/// - The first field (`reserved`) occupies the highest bits.
156/// - The last field (`sequence`) occupies the lowest bits.
157/// - The total number of bits **must exactly equal** the size of the backing
158///   integer type (`u64`, `u128`, etc.). If it doesn't, the macro will trigger
159///   a compile-time assertion failure.
160///
161/// ```text
162/// define_snowflake_id!(
163///     <TypeName>, <IntegerType>,
164///     reserved: <bits>,
165///     timestamp: <bits>,
166///     machine_id: <bits>,
167///     sequence: <bits>
168/// );
169///```
170///
171/// ## Example: A Twitter-like layout
172/// ```rust
173/// use ferroid::define_snowflake_id;
174///
175/// define_snowflake_id!(
176///     MyCustomId, u64,
177///     reserved: 1,
178///     timestamp: 41,
179///     machine_id: 10,
180///     sequence: 12
181/// );
182/// ```
183///
184/// Which expands to the following bit layout:
185///
186/// ```text
187///  Bit Index:  63           63 62            22 21             12 11             0
188///              +--------------+----------------+-----------------+---------------+
189///  Field:      | reserved (1) | timestamp (41) | machine ID (10) | sequence (12) |
190///              +--------------+----------------+-----------------+---------------+
191///              |<----------- MSB ---------- 64 bits ----------- LSB ------------>|
192/// ```
193#[macro_export]
194macro_rules! define_snowflake_id {
195    (
196        $(#[$meta:meta])*
197        $name:ident, $int:ty,
198        reserved: $reserved_bits:expr,
199        timestamp: $timestamp_bits:expr,
200        machine_id: $machine_bits:expr,
201        sequence: $sequence_bits:expr
202    ) => {
203        $(#[$meta])*
204        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
205        pub struct $name {
206            id: $int,
207        }
208
209        const _: () = {
210            // Compile-time check: total bit width _must_ equal the backing
211            // type. This is to avoid aliasing surprises.
212            assert!(
213                $reserved_bits + $timestamp_bits + $machine_bits + $sequence_bits == <$int>::BITS,
214                "Snowflake layout overflows the underlying integer type"
215            );
216        };
217
218        impl $name {
219            pub const RESERVED_BITS: $int = $reserved_bits;
220            pub const TIMESTAMP_BITS: $int = $timestamp_bits;
221            pub const MACHINE_ID_BITS: $int = $machine_bits;
222            pub const SEQUENCE_BITS: $int = $sequence_bits;
223
224            pub const SEQUENCE_SHIFT: $int = 0;
225            pub const MACHINE_ID_SHIFT: $int = Self::SEQUENCE_SHIFT + Self::SEQUENCE_BITS;
226            pub const TIMESTAMP_SHIFT: $int = Self::MACHINE_ID_SHIFT + Self::MACHINE_ID_BITS;
227            pub const RESERVED_SHIFT: $int = Self::TIMESTAMP_SHIFT + Self::TIMESTAMP_BITS;
228
229            pub const RESERVED_MASK: $int = ((1 << Self::RESERVED_BITS) - 1);
230            pub const TIMESTAMP_MASK: $int = ((1 << Self::TIMESTAMP_BITS) - 1);
231            pub const MACHINE_ID_MASK: $int = ((1 << Self::MACHINE_ID_BITS) - 1);
232            pub const SEQUENCE_MASK: $int = ((1 << Self::SEQUENCE_BITS) - 1);
233
234            pub const fn from(timestamp: $int, machine_id: $int, sequence: $int) -> Self {
235                let t = (timestamp & Self::TIMESTAMP_MASK) << Self::TIMESTAMP_SHIFT;
236                let m = (machine_id & Self::MACHINE_ID_MASK) << Self::MACHINE_ID_SHIFT;
237                let s = (sequence & Self::SEQUENCE_MASK) << Self::SEQUENCE_SHIFT;
238                Self { id: t | m | s }
239            }
240
241            /// Extracts the timestamp from the packed ID.
242            pub const fn timestamp(&self) -> $int {
243                (self.id >> Self::TIMESTAMP_SHIFT) & Self::TIMESTAMP_MASK
244            }
245            /// Extracts the machine ID from the packed ID.
246            pub const fn machine_id(&self) -> $int {
247                (self.id >> Self::MACHINE_ID_SHIFT) & Self::MACHINE_ID_MASK
248            }
249            /// Extracts the sequence number from the packed ID.
250            pub const fn sequence(&self) -> $int {
251                (self.id >> Self::SEQUENCE_SHIFT) & Self::SEQUENCE_MASK
252            }
253        }
254
255        impl $crate::Snowflake for $name {
256            type Ty = $int;
257
258            const ZERO: $int = 0;
259            const ONE: $int = 1;
260
261            fn timestamp(&self) -> Self::Ty {
262                self.timestamp()
263            }
264
265            fn machine_id(&self) -> Self::Ty {
266                self.machine_id()
267            }
268
269            fn sequence(&self) -> Self::Ty {
270                self.sequence()
271            }
272
273            fn max_timestamp() -> Self::Ty {
274                (1 << $timestamp_bits) - 1
275            }
276
277            fn max_machine_id() -> Self::Ty {
278                (1 << $machine_bits) - 1
279            }
280
281            fn max_sequence() -> Self::Ty {
282                (1 << $sequence_bits) - 1
283            }
284
285            fn from_components(timestamp: $int, machine_id: $int, sequence: $int) -> Self {
286                debug_assert!(timestamp <= Self::TIMESTAMP_MASK, "timestamp overflow");
287                debug_assert!(machine_id <= Self::MACHINE_ID_MASK, "machine_id overflow");
288                debug_assert!(sequence <= Self::SEQUENCE_MASK, "sequence overflow");
289                Self::from(timestamp, machine_id, sequence)
290            }
291
292            fn to_raw(&self) -> Self::Ty {
293                self.id
294            }
295
296            fn from_raw(raw: Self::Ty) -> Self {
297                 Self { id: raw }
298            }
299
300            fn to_padded_string(&self) -> String {
301                let max = Self::Ty::MAX;
302                let mut n = max;
303                let mut digits = 1;
304                while n >= 10 {
305                    n = n / 10;
306                    digits += 1;
307                }
308                format!("{:0width$}", self.to_raw(), width = digits)
309            }
310        }
311
312        impl core::fmt::Display for $name {
313            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
314                write!(f, "{}", self.id)
315            }
316        }
317
318        impl core::fmt::Debug for $name {
319            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
320                let full = core::any::type_name::<Self>();
321                let name = full.rsplit("::").next().unwrap_or(full);
322                let mut dbg = f.debug_struct(name);
323                dbg.field("id", &format_args!("{:} (0x{:x})", self.to_raw(), self.to_raw()));
324
325                use $crate::Snowflake;
326                dbg.field("padded", &self.to_padded_string());
327
328                #[cfg(feature = "base32")]
329                {
330                    use $crate::SnowflakeBase32Ext;
331                    dbg.field("base32", &self.encode());
332                }
333
334                dbg.field("timestamp", &format_args!("{:} (0x{:x})", self.timestamp(), self.timestamp()));
335                dbg.field("machine_id", &format_args!("{:} (0x{:x})", self.machine_id(), self.machine_id()));
336                dbg.field("sequence", &format_args!("{:} (0x{:x})", self.sequence(), self.sequence()));
337
338                dbg.finish()
339            }
340        }
341    };
342}
343
344define_snowflake_id!(
345    /// A 64-bit Snowflake ID using the Twitter layout
346    ///
347    /// - 1 bit reserved
348    /// - 41 bits timestamp (ms since [`TWITTER_EPOCH`])
349    /// - 10 bits machine ID (worker ID (5) and process ID (5))
350    /// - 12 bits sequence
351    ///
352    /// ```text
353    ///  Bit Index:  63           63 62            22 21             12 11             0
354    ///              +--------------+----------------+-----------------+---------------+
355    ///  Field:      | reserved (1) | timestamp (41) | machine ID (10) | sequence (12) |
356    ///              +--------------+----------------+-----------------+---------------+
357    ///              |<----------- MSB ---------- 64 bits ----------- LSB ------------>|
358    /// ```
359    /// [`TWITTER_EPOCH`]: crate::TWITTER_EPOCH
360    SnowflakeTwitterId, u64,
361    reserved: 1,
362    timestamp: 41,
363    machine_id: 10,
364    sequence: 12
365);
366
367define_snowflake_id!(
368    /// A 64-bit Snowflake ID using the Discord layout
369    ///
370    /// - 42 bits timestamp (ms since [`DISCORD_EPOCH`])
371    /// - 10 bits machine ID (worker ID (5) and process ID (5))
372    /// - 12 bits sequence
373    ///
374    /// ```text
375    ///  Bit Index:  63             22 21             12 11             0
376    ///              +----------------+-----------------+---------------+
377    ///  Field:      | timestamp (42) | machine ID (10) | sequence (12) |
378    ///              +----------------+-----------------+---------------+
379    ///              |<----- MSB ---------- 64 bits --------- LSB ----->|
380    /// ```
381    /// [`DISCORD_EPOCH`]: crate::DISCORD_EPOCH
382    SnowflakeDiscordId, u64,
383    reserved: 0,
384    timestamp: 42,
385    machine_id: 10,
386    sequence: 12
387);
388
389define_snowflake_id!(
390    /// A 64-bit Snowflake ID using the Mastodon layout
391    ///
392    /// - 48 bits timestamp (ms since [`MASTODON_EPOCH`])
393    /// - 16 bits sequence
394    ///
395    /// ```text
396    ///  Bit Index:  63             16 15             0
397    ///              +----------------+---------------+
398    ///  Field:      | timestamp (48) | sequence (16) |
399    ///              +----------------+---------------+
400    ///              |<--- MSB --- 64 bits -- LSB --->|
401    /// ```
402    /// [`MASTODON_EPOCH`]: crate::MASTODON_EPOCH
403    SnowflakeMastodonId, u64,
404    reserved: 0,
405    timestamp: 48,
406    machine_id: 0,
407    sequence: 16
408);
409
410define_snowflake_id!(
411    /// A 64-bit Snowflake ID using the Instagram layout
412    ///
413    /// - 41 bits timestamp (ms since [`INSTAGRAM_EPOCH`])
414    /// - 13 bits machine ID
415    /// - 10 bits sequence
416    ///
417    /// ```text
418    ///  Bit Index:  63             23 22             10 9              0
419    ///              +----------------+-----------------+---------------+
420    ///  Field:      | timestamp (41) | machine ID (13) | sequence (10) |
421    ///              +----------------+-----------------+---------------+
422    ///              |<----- MSB ---------- 64 bits --------- LSB ----->|
423    /// ```
424    /// [`INSTAGRAM_EPOCH`]: crate::INSTAGRAM_EPOCH
425    SnowflakeInstagramId, u64,
426    reserved: 0,
427    timestamp: 41,
428    machine_id: 13,
429    sequence: 10
430);
431
432define_snowflake_id!(
433    /// A 128-bit Snowflake ID using a hybrid layout.
434    ///
435    /// - 40 bits reserved
436    /// - 48 bits timestamp (ms since [`CUSTOM_EPOCH`])
437    /// - 20 bits machine ID
438    /// - 20 bits sequence
439    ///
440    /// ```text
441    ///  Bit Index:  127                88 87            40 39             20 19             0
442    ///              +--------------------+----------------+-----------------+---------------+
443    ///  Field:      | reserved (40 bits) | timestamp (48) | machine ID (20) | sequence (20) |
444    ///              +--------------------+----------------+-----------------+---------------+
445    ///              |<--- HI 64 bits --->|<------------------- LO 64 bits ----------------->|
446    ///              |<- MSB ------ LSB ->|<----- MSB ---------- 64 bits --------- LSB ----->|
447    /// ```
448    /// [`CUSTOM_EPOCH`]: crate::CUSTOM_EPOCH
449    SnowflakeLongId, u128,
450    reserved: 40,
451    timestamp: 48,
452    machine_id: 20,
453    sequence: 20
454);
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    #[test]
461    fn test_snowflake_twitter_id_fields_and_bounds() {
462        let ts = SnowflakeTwitterId::max_timestamp();
463        let mid = SnowflakeTwitterId::max_machine_id();
464        let seq = SnowflakeTwitterId::max_sequence();
465
466        let id = SnowflakeTwitterId::from(ts, mid, seq);
467        println!("ID: {:#?}", id);
468        assert_eq!(id.timestamp(), ts);
469        assert_eq!(id.machine_id(), mid);
470        assert_eq!(id.sequence(), seq);
471        assert_eq!(SnowflakeTwitterId::from_components(ts, mid, seq), id);
472    }
473
474    #[test]
475    fn test_snowflake_discord_id_fields_and_bounds() {
476        let ts = SnowflakeDiscordId::max_timestamp();
477        let mid = SnowflakeDiscordId::max_machine_id();
478        let seq = SnowflakeDiscordId::max_sequence();
479
480        let id = SnowflakeDiscordId::from(ts, mid, seq);
481        println!("ID: {:#?}", id);
482        assert_eq!(id.timestamp(), ts);
483        assert_eq!(id.machine_id(), mid);
484        assert_eq!(id.sequence(), seq);
485        assert_eq!(SnowflakeDiscordId::from_components(ts, mid, seq), id);
486    }
487
488    #[test]
489    fn test_snowflake_mastodon_id_fields_and_bounds() {
490        let ts = SnowflakeMastodonId::max_timestamp();
491        let mid = SnowflakeMastodonId::max_machine_id();
492        let seq = SnowflakeMastodonId::max_sequence();
493
494        let id = SnowflakeMastodonId::from(ts, mid, seq);
495        println!("ID: {:#?}", id);
496        assert_eq!(id.timestamp(), ts);
497        assert_eq!(id.machine_id(), 0); // machine_id is always zero
498        assert_eq!(id.sequence(), seq);
499        assert_eq!(SnowflakeMastodonId::from_components(ts, 0, seq), id);
500    }
501
502    #[test]
503    fn test_snowflake_instagram_id_fields_and_bounds() {
504        let ts = SnowflakeInstagramId::max_timestamp();
505        let mid = SnowflakeInstagramId::max_machine_id();
506        let seq = SnowflakeInstagramId::max_sequence();
507
508        let id = SnowflakeInstagramId::from(ts, mid, seq);
509        println!("ID: {:#?}", id);
510        assert_eq!(id.timestamp(), ts);
511        assert_eq!(id.machine_id(), mid);
512        assert_eq!(id.sequence(), seq);
513        assert_eq!(SnowflakeInstagramId::from_components(ts, mid, seq), id);
514    }
515
516    #[test]
517    fn test_snowflake_long_id_fields_and_bounds() {
518        let ts = SnowflakeLongId::max_timestamp();
519        let mid = SnowflakeLongId::max_machine_id();
520        let seq = SnowflakeLongId::max_sequence();
521
522        let id = SnowflakeLongId::from(ts, mid, seq);
523        println!("ID: {:#?}", id);
524        assert_eq!(id.timestamp(), ts);
525        assert_eq!(id.machine_id(), mid);
526        assert_eq!(id.sequence(), seq);
527        assert_eq!(SnowflakeLongId::from_components(ts, mid, seq), id);
528    }
529
530    #[test]
531    #[should_panic(expected = "timestamp overflow")]
532    fn twitter_timestamp_overflow_panics() {
533        let ts = SnowflakeTwitterId::max_timestamp() + 1;
534        SnowflakeTwitterId::from_components(ts, 0, 0);
535    }
536
537    #[test]
538    #[should_panic(expected = "machine_id overflow")]
539    fn twitter_machine_id_overflow_panics() {
540        let mid = SnowflakeTwitterId::max_machine_id() + 1;
541        SnowflakeTwitterId::from_components(0, mid, 0);
542    }
543
544    #[test]
545    #[should_panic(expected = "sequence overflow")]
546    fn twitter_sequence_overflow_panics() {
547        let seq = SnowflakeTwitterId::max_sequence() + 1;
548        SnowflakeTwitterId::from_components(0, 0, seq);
549    }
550
551    #[test]
552    #[should_panic(expected = "timestamp overflow")]
553    fn discord_timestamp_overflow_panics() {
554        let ts = SnowflakeDiscordId::max_timestamp() + 1;
555        SnowflakeDiscordId::from_components(ts, 0, 0);
556    }
557
558    #[test]
559    #[should_panic(expected = "machine_id overflow")]
560    fn discord_machine_id_overflow_panics() {
561        let mid = SnowflakeDiscordId::max_machine_id() + 1;
562        SnowflakeDiscordId::from_components(0, mid, 0);
563    }
564
565    #[test]
566    #[should_panic(expected = "sequence overflow")]
567    fn discord_sequence_overflow_panics() {
568        let seq = SnowflakeDiscordId::max_sequence() + 1;
569        SnowflakeDiscordId::from_components(0, 0, seq);
570    }
571
572    #[test]
573    #[should_panic(expected = "timestamp overflow")]
574    fn mastodon_timestamp_overflow_panics() {
575        let ts = SnowflakeMastodonId::max_timestamp() + 1;
576        SnowflakeMastodonId::from_components(ts, 0, 0);
577    }
578
579    #[test]
580    #[should_panic(expected = "machine_id overflow")]
581    fn mastodon_machine_id_overflow_panics() {
582        let mid = SnowflakeMastodonId::max_machine_id() + 1;
583        SnowflakeMastodonId::from_components(0, mid, 0);
584    }
585
586    #[test]
587    #[should_panic(expected = "sequence overflow")]
588    fn mastodon_sequence_overflow_panics() {
589        let seq = SnowflakeMastodonId::max_sequence() + 1;
590        SnowflakeMastodonId::from_components(0, 0, seq);
591    }
592
593    #[test]
594    #[should_panic(expected = "timestamp overflow")]
595    fn instagram_timestamp_overflow_panics() {
596        let ts = SnowflakeInstagramId::max_timestamp() + 1;
597        SnowflakeInstagramId::from_components(ts, 0, 0);
598    }
599
600    #[test]
601    #[should_panic(expected = "machine_id overflow")]
602    fn instagram_machine_id_overflow_panics() {
603        let mid = SnowflakeInstagramId::max_machine_id() + 1;
604        SnowflakeInstagramId::from_components(0, mid, 0);
605    }
606
607    #[test]
608    #[should_panic(expected = "sequence overflow")]
609    fn instagram_sequence_overflow_panics() {
610        let seq = SnowflakeInstagramId::max_sequence() + 1;
611        SnowflakeInstagramId::from_components(0, 0, seq);
612    }
613
614    #[test]
615    #[should_panic(expected = "timestamp overflow")]
616    fn long_timestamp_overflow_panics() {
617        let ts = SnowflakeLongId::max_timestamp() + 1;
618        SnowflakeLongId::from_components(ts, 0, 0);
619    }
620
621    #[test]
622    #[should_panic(expected = "machine_id overflow")]
623    fn long_machine_id_overflow_panics() {
624        let mid = SnowflakeLongId::max_machine_id() + 1;
625        SnowflakeLongId::from_components(0, mid, 0);
626    }
627
628    #[test]
629    #[should_panic(expected = "sequence overflow")]
630    fn long_sequence_overflow_panics() {
631        let seq = SnowflakeLongId::max_sequence() + 1;
632        SnowflakeLongId::from_components(0, 0, seq);
633    }
634
635    #[test]
636    fn twitter_low_bit_fields() {
637        let id = SnowflakeTwitterId::from_components(0, 0, 0);
638        assert_eq!(id.timestamp(), 0);
639        assert_eq!(id.machine_id(), 0);
640        assert_eq!(id.sequence(), 0);
641
642        let id = SnowflakeTwitterId::from_components(1, 1, 1);
643        assert_eq!(id.timestamp(), 1);
644        assert_eq!(id.machine_id(), 1);
645        assert_eq!(id.sequence(), 1);
646    }
647
648    #[test]
649    fn discord_low_bit_fields() {
650        let id = SnowflakeDiscordId::from_components(0, 0, 0);
651        assert_eq!(id.timestamp(), 0);
652        assert_eq!(id.machine_id(), 0);
653        assert_eq!(id.sequence(), 0);
654
655        let id = SnowflakeDiscordId::from_components(1, 1, 1);
656        assert_eq!(id.timestamp(), 1);
657        assert_eq!(id.machine_id(), 1);
658        assert_eq!(id.sequence(), 1);
659    }
660
661    #[test]
662    fn mastodon_low_bit_fields() {
663        let id = SnowflakeMastodonId::from_components(0, 0, 0);
664        assert_eq!(id.timestamp(), 0);
665        assert_eq!(id.machine_id(), 0);
666        assert_eq!(id.sequence(), 0);
667
668        let id = SnowflakeMastodonId::from_components(1, 0, 1);
669        assert_eq!(id.timestamp(), 1);
670        assert_eq!(id.machine_id(), 0); // always zero
671        assert_eq!(id.sequence(), 1);
672    }
673
674    #[test]
675    fn instagram_low_bit_fields() {
676        let id = SnowflakeInstagramId::from_components(0, 0, 0);
677        assert_eq!(id.timestamp(), 0);
678        assert_eq!(id.machine_id(), 0);
679        assert_eq!(id.sequence(), 0);
680
681        let id = SnowflakeInstagramId::from_components(1, 1, 1);
682        assert_eq!(id.timestamp(), 1);
683        assert_eq!(id.machine_id(), 1);
684        assert_eq!(id.sequence(), 1);
685    }
686
687    #[test]
688    fn long_low_bit_fields() {
689        let id = SnowflakeLongId::from_components(0, 0, 0);
690        assert_eq!(id.timestamp(), 0);
691        assert_eq!(id.machine_id(), 0);
692        assert_eq!(id.sequence(), 0);
693
694        let id = SnowflakeLongId::from_components(1, 1, 1);
695        assert_eq!(id.timestamp(), 1);
696        assert_eq!(id.machine_id(), 1);
697        assert_eq!(id.sequence(), 1);
698    }
699
700    #[test]
701    fn twitter_edge_rollover() {
702        let id = SnowflakeTwitterId::from_components(0, 0, SnowflakeTwitterId::max_sequence());
703        assert_eq!(id.sequence(), SnowflakeTwitterId::max_sequence());
704
705        let id = SnowflakeTwitterId::from_components(0, SnowflakeTwitterId::max_machine_id(), 0);
706        assert_eq!(id.machine_id(), SnowflakeTwitterId::max_machine_id());
707    }
708
709    #[test]
710    fn discord_edge_rollover() {
711        let id = SnowflakeDiscordId::from_components(0, 0, SnowflakeDiscordId::max_sequence());
712        assert_eq!(id.sequence(), SnowflakeDiscordId::max_sequence());
713
714        let id = SnowflakeDiscordId::from_components(0, SnowflakeDiscordId::max_machine_id(), 0);
715        assert_eq!(id.machine_id(), SnowflakeDiscordId::max_machine_id());
716    }
717
718    #[test]
719    fn mastodon_edge_rollover() {
720        let id = SnowflakeMastodonId::from_components(0, 0, SnowflakeMastodonId::max_sequence());
721        assert_eq!(id.sequence(), SnowflakeMastodonId::max_sequence());
722    }
723
724    #[test]
725    fn instagram_edge_rollover() {
726        let id = SnowflakeInstagramId::from_components(0, 0, SnowflakeInstagramId::max_sequence());
727        assert_eq!(id.sequence(), SnowflakeInstagramId::max_sequence());
728
729        let id =
730            SnowflakeInstagramId::from_components(0, SnowflakeInstagramId::max_machine_id(), 0);
731        assert_eq!(id.machine_id(), SnowflakeInstagramId::max_machine_id());
732    }
733
734    #[test]
735    fn long_edge_rollover() {
736        let id = SnowflakeLongId::from_components(0, 0, SnowflakeLongId::max_sequence());
737        assert_eq!(id.sequence(), SnowflakeLongId::max_sequence());
738
739        let id = SnowflakeLongId::from_components(0, SnowflakeLongId::max_machine_id(), 0);
740        assert_eq!(id.machine_id(), SnowflakeLongId::max_machine_id());
741    }
742}