beakid 0.1.1

Lock-free unique ID generator inspired by Twitter Snowflake
Documentation
/// En: A unique 64-bit identifier produced by [`Generator`](crate::Generator).
///
/// Implements [`Ord`] — IDs generated by the same [`Generator`](crate::Generator)
/// instance on the same thread are strictly increasing, so sorting a collection
/// of `BeakId`s recovers approximate creation order.
///
/// Ru: Уникальный 64-битный идентификатор, созданный [`Generator`](crate::Generator).
///
/// Реализует [`Ord`] — ID, сгенерированные одним экземпляром [`Generator`](crate::Generator)
/// в одном потоке, строго возрастают, поэтому сортировка коллекции `BeakId`
/// восстанавливает приближённый порядок создания.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BeakId(u64);

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

impl BeakId {
    /// En: Wraps a raw `u64` value as a `BeakId`.
    ///
    /// Intended for deserialisation or reconstruction from storage.
    /// Does not validate that `v` was produced by a [`Generator`](crate::Generator).
    ///
    /// Ru: Оборачивает сырое значение `u64` в `BeakId`.
    ///
    /// Предназначен для десериализации или восстановления из хранилища.
    /// Не проверяет, что `v` был создан [`Generator`](crate::Generator).
    pub fn new(v: u64) -> Self {
        BeakId(v)
    }

    /// En: Decodes a `BeakId` from an 11-character base62 string.
    ///
    /// The alphabet is `0–9`, `A–Z`, `a–z` (digits first, then uppercase, then lowercase).
    ///
    /// # Errors
    ///
    /// Returns [`Error::InvalidBase62`](crate::Error::InvalidBase62) if:
    /// - the string length is not exactly 11 characters, or
    /// - the string contains a character outside the base62 alphabet, or
    /// - the encoded value overflows `u64`.
    ///
    /// Ru: Декодирует `BeakId` из 11-символьной строки в кодировке base62.
    ///
    /// Алфавит: `0–9`, `A–Z`, `a–z` (сначала цифры, затем верхний регистр, затем нижний).
    ///
    /// # Ошибки
    ///
    /// Возвращает [`Error::InvalidBase62`](crate::Error::InvalidBase62) если:
    /// - длина строки не равна ровно 11 символам,
    /// - строка содержит символ вне алфавита base62, или
    /// - закодированное значение переполняет `u64`.
    pub fn from_base62(s: &str) -> Result<Self, crate::Error> {
        const LEN: usize = 11;

        if s.len() != LEN {
            return Err(crate::Error::InvalidBase62);
        }

        let mut n = 0u64;
        for &byte in s.as_bytes() {
            let digit = match byte {
                b'0'..=b'9' => (byte - b'0') as u64,
                b'A'..=b'Z' => (byte - b'A' + 10) as u64,
                b'a'..=b'z' => (byte - b'a' + 36) as u64,
                _ => return Err(crate::Error::InvalidBase62),
            };
            n = n
                .checked_mul(62)
                .and_then(|n| n.checked_add(digit))
                .ok_or(crate::Error::InvalidBase62)?;
        }

        Ok(BeakId(n))
    }

    /// En: Encodes the ID as an 11-character base62 string.
    ///
    /// The output is always exactly 11 characters, left-padded with `'0'` for
    /// small values. The alphabet is `0–9`, `A–Z`, `a–z`.
    /// The encoded form is reversible via [`from_base62`](BeakId::from_base62).
    ///
    /// Ru: Кодирует ID в 11-символьную строку base62.
    ///
    /// Результат всегда ровно 11 символов, дополняется слева символами `'0'`
    /// для малых значений. Алфавит: `0–9`, `A–Z`, `a–z`.
    /// Закодированная форма обратима через [`from_base62`](BeakId::from_base62).
    pub fn base62(&self) -> String {
        const ALPHABET: &[u8; 62] =
            b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        const LEN: usize = 11;

        let mut buf = [b'0'; LEN];
        let mut n = self.0;

        for i in (0..LEN).rev() {
            buf[i] = ALPHABET[(n % 62) as usize];
            n /= 62;
        }

        // SAFETY: buf содержит только байты из ASCII-алфавита
        unsafe { String::from_utf8_unchecked(buf.to_vec()) }
    }
}