ferroid/id/
snowflake.rs

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