const-uuid 0.1.0

Lightweight UUID container for const contexts
Documentation

use std::str::FromStr;

pub use const_uuid_proc_macros::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConstUuidError {
    err: &'static str,
}

impl ConstUuidError {
    pub fn new(err: &'static str) -> Self {
        Self { err }
    }
}

impl std::fmt::Display for ConstUuidError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.err)
    }
}

impl std::error::Error for ConstUuidError {}

/// Lightweight UUID container for const contexts.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConstUuid(pub u128);

impl Default for ConstUuid {
    fn default() -> Self {
        ConstUuid(0)
    }
}

impl FromStr for ConstUuid {
    type Err = ConstUuidError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let s = s.trim();
        if s.len() != 36 {
            return Err(ConstUuidError::new("Invalid UUID length"));
        }

        let mut bytes = [0u8; 16];
        let mut i = 0;
        for (j, c) in s.chars().enumerate() {
            if j == 8 || j == 13 || j == 18 || j == 23 {
                if c != '-' {
                    return Err(ConstUuidError::new("Invalid UUID format"));
                }
                continue;
            }

            if c.is_ascii_hexdigit() {
                let b = c.to_digit(16).unwrap() as u8;
                if i % 2 == 0 {
                    bytes[i / 2] = b << 4;
                } else {
                    bytes[i / 2] |= b;
                }
                i += 1;
            } else {
                return Err(ConstUuidError::new("Invalid UUID format"));
            }
        }

        Ok(ConstUuid(u128::from_be_bytes(bytes)))
    }
}

impl std::fmt::Display for ConstUuid {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let bytes = self.0.to_be_bytes();
        let string = bytes.iter().map(|b| format!("{:02x}", b)).collect::<String>();
        write!(f, "{}-{}-{}-{}-{}", &string[0..8], &string[8..12], &string[12..16], &string[16..20], &string[20..32])
    }
}

impl std::fmt::Debug for ConstUuid {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "ConstUuid({})", self)
    }
}

impl ConstUuid {
    pub const NIL: ConstUuid = ConstUuid(0);

    /// Extracts the inner `u128` value.
    pub fn to_u128(&self) -> u128 {
        self.0
    }

    /// Converts the UUID to a byte array.
    pub fn to_bytes(&self) -> [u8; 16] {
        self.0.to_be_bytes()
    }

    /// Checks if the UUID is nil.
    pub fn is_nil(&self) -> bool {
        self.0 == 0
    }
}

/// Creates a `ConstUuid` from a string literal.
#[macro_export]
macro_rules! const_uuid {
    ($uuid:expr) => {
        $crate::ConstUuid($crate::const_uuid_u128!($uuid))
    };
}

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

    #[test]
    fn default() {
        let uuid = ConstUuid::default();
        assert_eq!(uuid.to_u128(), 0);
        let parsed = const_uuid!("00000000-0000-0000-0000-000000000000");
        assert_eq!(uuid, parsed);
    }

    #[test]
    fn macro_vs_parse() {
        let uuid = const_uuid!("01234567-89ab-cdef-0123-456789abcdef");
        let parsed = ConstUuid::from_str("01234567-89ab-cdef-0123-456789abcdef").unwrap();
        assert_eq!(uuid, parsed);
    }

    #[test]
    fn parse_and_back() {
        let uuid = ConstUuid::from_str("01234567-89ab-cdef-0123-456789abcdef").unwrap();
        assert_eq!(uuid.to_string(), "01234567-89ab-cdef-0123-456789abcdef");
    }
}