refluxer 0.2.0

Rust API wrapper for Fluxer
Documentation
//! Fluxer snowflake ID type with introspection, validation, and serde support.
//!
//! A snowflake is a 64-bit unique identifier that encodes a timestamp and
//! internal metadata. This crate provides the core [`Snowflake`] type and
//! a [`snowflake_id!`] macro for creating typed wrappers.
//!
//! # Examples
//!
//! ```
//! use refluxer::Snowflake;
//!
//! let sf = Snowflake::new(1491150377518758430);
//! assert!(sf.is_valid());
//! assert!(sf.timestamp_ms() > 0);
//!
//! // Roundtrip through parts
//! let rebuilt = Snowflake::from_parts(
//!     sf.timestamp_ms(), sf.worker_id(), sf.process_id(), sf.increment(),
//! );
//! assert_eq!(sf, rebuilt);
//! ```

use std::time::{Duration, SystemTime, UNIX_EPOCH};

use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// Fluxer epoch: January 1, 2015 00:00:00 UTC (same as Discord).
pub const FLUXER_EPOCH_MS: u64 = 1_420_070_400_000;

/// A Fluxer snowflake — a 64-bit unique identifier encoding a timestamp
/// and internal metadata.
///
/// # Layout (MSB → LSB)
///
/// | Bits  | Field      | Description                              |
/// |-------|------------|------------------------------------------|
/// | 63–22 | Timestamp  | Milliseconds since Fluxer epoch (42 bit) |
/// | 21–17 | Worker ID  | Internal worker ID (5 bit)               |
/// | 16–12 | Process ID | Internal process ID (5 bit)              |
/// | 11–0  | Increment  | Per-process counter (12 bit)             |
///
/// # Serde
///
/// Deserializes from both string `"1234"` and number `1234` (Fluxer sends
/// strings). Serializes as a string to match the API convention.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Snowflake(u64);

impl Snowflake {
    /// Create a snowflake from a raw u64 value.
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    /// Build a snowflake from its constituent parts.
    ///
    /// - `timestamp_ms` — milliseconds since the Fluxer epoch
    /// - `worker_id` — 5-bit worker ID (0–31)
    /// - `process_id` — 5-bit process ID (0–31)
    /// - `increment` — 12-bit increment (0–4095)
    pub const fn from_parts(
        timestamp_ms: u64,
        worker_id: u8,
        process_id: u8,
        increment: u16,
    ) -> Self {
        let value = (timestamp_ms << 22)
            | ((worker_id as u64 & 0x1F) << 17)
            | ((process_id as u64 & 0x1F) << 12)
            | (increment as u64 & 0xFFF);
        Self(value)
    }

    /// The raw u64 value.
    pub const fn value(self) -> u64 {
        self.0
    }

    /// Milliseconds elapsed since the Fluxer epoch when this snowflake was created.
    pub const fn timestamp_ms(self) -> u64 {
        self.0 >> 22
    }

    /// Unix timestamp in milliseconds.
    pub const fn unix_timestamp_ms(self) -> u64 {
        self.timestamp_ms() + FLUXER_EPOCH_MS
    }

    /// The moment this snowflake was created, as a `SystemTime`.
    pub fn created_at(self) -> SystemTime {
        UNIX_EPOCH + Duration::from_millis(self.unix_timestamp_ms())
    }

    /// Internal worker ID (5 bits, 0–31).
    pub const fn worker_id(self) -> u8 {
        ((self.0 >> 17) & 0x1F) as u8
    }

    /// Internal process ID (5 bits, 0–31).
    pub const fn process_id(self) -> u8 {
        ((self.0 >> 12) & 0x1F) as u8
    }

    /// Per-process increment (12 bits, 0–4095).
    pub const fn increment(self) -> u16 {
        (self.0 & 0xFFF) as u16
    }

    /// Basic validation: non-zero and timestamp not in the far future (> 1 year from now).
    pub fn is_valid(self) -> bool {
        if self.0 == 0 {
            return false;
        }
        let Ok(duration_since_epoch) = SystemTime::now().duration_since(UNIX_EPOCH) else {
            return false;
        };
        let now_ms = duration_since_epoch.as_millis() as u64;
        let one_year_ms = 365 * 24 * 60 * 60 * 1000;
        self.unix_timestamp_ms() <= now_ms + one_year_ms
    }

    /// The Fluxer epoch as a `SystemTime`.
    pub fn epoch() -> SystemTime {
        UNIX_EPOCH + Duration::from_millis(FLUXER_EPOCH_MS)
    }
}

impl std::fmt::Display for Snowflake {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<u64> for Snowflake {
    fn from(v: u64) -> Self {
        Self(v)
    }
}

impl From<Snowflake> for u64 {
    fn from(sf: Snowflake) -> Self {
        sf.0
    }
}

impl Serialize for Snowflake {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&self.0.to_string())
    }
}

impl<'de> Deserialize<'de> for Snowflake {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        use serde::de;

        struct Visitor;

        impl<'de> de::Visitor<'de> for Visitor {
            type Value = Snowflake;

            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.write_str("a snowflake ID as a string or number")
            }

            fn visit_u64<E: de::Error>(self, v: u64) -> Result<Snowflake, E> {
                Ok(Snowflake(v))
            }

            fn visit_i64<E: de::Error>(self, v: i64) -> Result<Snowflake, E> {
                u64::try_from(v)
                    .map(Snowflake)
                    .map_err(|_| E::custom(format!("negative snowflake: {v}")))
            }

            fn visit_str<E: de::Error>(self, v: &str) -> Result<Snowflake, E> {
                v.parse::<u64>()
                    .map(Snowflake)
                    .map_err(|_| E::custom(format!("invalid snowflake string: {v}")))
            }
        }

        deserializer.deserialize_any(Visitor)
    }
}

/// Define a typed snowflake ID wrapper.
///
/// Creates a newtype around [`Snowflake`] with `Deref`, `Display`, `From`,
/// and serde support. All [`Snowflake`] methods are accessible through `Deref`.
///
/// # Example
///
/// ```
/// use refluxer::{snowflake_id, Snowflake};
///
/// snowflake_id!(
///     /// Unique identifier for a user.
///     pub UserId
/// );
///
/// let id = UserId::new(1491150377518758430);
/// assert!(id.is_valid());
/// assert_eq!(id.worker_id(), Snowflake::new(1491150377518758430).worker_id());
/// ```
#[macro_export]
macro_rules! snowflake_id {
    ($(#[$meta:meta])* $vis:vis $name:ident) => {
        $(#[$meta])*
        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
        $vis struct $name(pub $crate::Snowflake);

        impl $name {
            /// Create from a raw u64 value.
            pub const fn new(value: u64) -> Self {
                Self($crate::Snowflake::new(value))
            }
        }

        impl std::ops::Deref for $name {
            type Target = $crate::Snowflake;
            fn deref(&self) -> &$crate::Snowflake {
                &self.0
            }
        }

        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                self.0.fmt(f)
            }
        }

        impl From<u64> for $name {
            fn from(v: u64) -> Self {
                Self($crate::Snowflake::new(v))
            }
        }

        impl From<$crate::Snowflake> for $name {
            fn from(sf: $crate::Snowflake) -> Self {
                Self(sf)
            }
        }

        impl From<$name> for u64 {
            fn from(id: $name) -> Self {
                id.0.value()
            }
        }

        impl From<$name> for $crate::Snowflake {
            fn from(id: $name) -> Self {
                id.0
            }
        }

        impl serde::Serialize for $name {
            fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                self.0.serialize(serializer)
            }
        }

        impl<'de> serde::Deserialize<'de> for $name {
            fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
                $crate::Snowflake::deserialize(deserializer).map(Self)
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    const EXAMPLE: u64 = 1491150377518758430;

    #[test]
    fn parts_roundtrip() {
        let sf = Snowflake::new(EXAMPLE);
        let rebuilt = Snowflake::from_parts(
            sf.timestamp_ms(),
            sf.worker_id(),
            sf.process_id(),
            sf.increment(),
        );
        assert_eq!(sf, rebuilt);
    }

    #[test]
    fn timestamp() {
        let sf = Snowflake::new(EXAMPLE);
        let ts = sf.unix_timestamp_ms();
        assert!(ts > 1_700_000_000_000);
        assert!(ts < 2_000_000_000_000);
    }

    #[test]
    fn worker_and_process_in_range() {
        let sf = Snowflake::new(EXAMPLE);
        assert!(sf.worker_id() <= 31);
        assert!(sf.process_id() <= 31);
        assert!(sf.increment() <= 4095);
    }

    #[test]
    fn validation() {
        assert!(!Snowflake::new(0).is_valid());
        assert!(Snowflake::new(EXAMPLE).is_valid());
        assert!(!Snowflake::new(u64::MAX).is_valid());
    }

    #[test]
    fn from_parts() {
        let sf = Snowflake::from_parts(1000, 5, 10, 42);
        assert_eq!(sf.timestamp_ms(), 1000);
        assert_eq!(sf.worker_id(), 5);
        assert_eq!(sf.process_id(), 10);
        assert_eq!(sf.increment(), 42);
    }

    #[test]
    fn serde_from_string() {
        let sf: Snowflake = serde_json::from_str("\"1491150377518758430\"").unwrap();
        assert_eq!(sf.value(), EXAMPLE);
    }

    #[test]
    fn serde_from_number() {
        let sf: Snowflake = serde_json::from_str("1491150377518758430").unwrap();
        assert_eq!(sf.value(), EXAMPLE);
    }

    #[test]
    fn serde_serializes_as_string() {
        let sf = Snowflake::new(EXAMPLE);
        let json = serde_json::to_string(&sf).unwrap();
        assert_eq!(json, format!("\"{EXAMPLE}\""));
    }

    snowflake_id!(pub TestId);

    #[test]
    fn typed_id_deref() {
        let id = TestId::new(EXAMPLE);
        assert_eq!(id.timestamp_ms(), Snowflake::new(EXAMPLE).timestamp_ms());
        assert_eq!(id.worker_id(), Snowflake::new(EXAMPLE).worker_id());
        assert!(id.is_valid());
    }

    #[test]
    fn typed_id_from_u64() {
        let id: TestId = EXAMPLE.into();
        assert_eq!(id.value(), EXAMPLE);
    }

    #[test]
    fn typed_id_serde_roundtrip() {
        let id = TestId::new(EXAMPLE);
        let json = serde_json::to_string(&id).unwrap();
        let parsed: TestId = serde_json::from_str(&json).unwrap();
        assert_eq!(id, parsed);
    }
}