avila_id/
lib.rs

1//! Avila ID - AVL Platform unique identifier
2//! Replacement for uuid crate - 100% Rust std
3//! Generates RFC 4122 compliant UUIDs (v4 - random)
4
5use std::fmt;
6use std::str::FromStr;
7
8/// 128-bit unique identifier (UUID v4)
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Id([u8; 16]);
11
12impl Id {
13    /// Generate a new random ID (UUIDv4)
14    pub fn new() -> Self {
15        let mut bytes = [0u8; 16];
16
17        // Use std random (básico) - em produção usar getrandom/OsRng
18        use std::collections::hash_map::RandomState;
19        use std::hash::{BuildHasher, Hasher};
20
21        let hasher1 = RandomState::new().build_hasher();
22        let hasher2 = RandomState::new().build_hasher();
23
24        let h1 = hasher1.finish();
25        let h2 = hasher2.finish();
26
27        bytes[0..8].copy_from_slice(&h1.to_le_bytes());
28        bytes[8..16].copy_from_slice(&h2.to_le_bytes());
29
30        // Set version (4) and variant (RFC 4122)
31        bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
32        bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC 4122
33
34        Self(bytes)
35    }
36
37    /// Parse from string (hyphenated format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
38    pub fn parse(s: &str) -> Result<Self, ParseError> {
39        let s = s.replace("-", "");
40        if s.len() != 32 {
41            return Err(ParseError::InvalidLength);
42        }
43
44        let mut bytes = [0u8; 16];
45        for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
46            let hex = std::str::from_utf8(chunk).map_err(|_| ParseError::InvalidChar)?;
47            bytes[i] = u8::from_str_radix(hex, 16).map_err(|_| ParseError::InvalidChar)?;
48        }
49
50        Ok(Self(bytes))
51    }
52
53    /// Get bytes representation
54    pub fn as_bytes(&self) -> &[u8; 16] {
55        &self.0
56    }
57
58    /// Convert to hyphenated string
59    pub fn to_string(&self) -> String {
60        format!(
61            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
62            self.0[0], self.0[1], self.0[2], self.0[3],
63            self.0[4], self.0[5],
64            self.0[6], self.0[7],
65            self.0[8], self.0[9],
66            self.0[10], self.0[11], self.0[12], self.0[13], self.0[14], self.0[15]
67        )
68    }
69
70    /// Nil/empty ID
71    pub fn nil() -> Self {
72        Self([0u8; 16])
73    }
74
75    /// Check if this is nil
76    pub fn is_nil(&self) -> bool {
77        self.0.iter().all(|&b| b == 0)
78    }
79}
80
81impl Default for Id {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87impl fmt::Display for Id {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(f, "{}", self.to_string())
90    }
91}
92
93impl FromStr for Id {
94    type Err = ParseError;
95
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        Self::parse(s)
98    }
99}
100
101#[derive(Debug, Clone)]
102pub enum ParseError {
103    InvalidLength,
104    InvalidChar,
105}
106
107impl fmt::Display for ParseError {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            ParseError::InvalidLength => write!(f, "Invalid ID length"),
111            ParseError::InvalidChar => write!(f, "Invalid character in ID"),
112        }
113    }
114}
115
116impl std::error::Error for ParseError {}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_id_generation() {
124        let id1 = Id::new();
125        let id2 = Id::new();
126        assert_ne!(id1, id2);
127    }
128
129    #[test]
130    fn test_id_parse() {
131        let id = Id::new();
132        let s = id.to_string();
133        let parsed = Id::parse(&s).unwrap();
134        assert_eq!(id, parsed);
135    }
136
137    #[test]
138    fn test_nil() {
139        let nil = Id::nil();
140        assert!(nil.is_nil());
141        assert_eq!(nil.to_string(), "00000000-0000-0000-0000-000000000000");
142    }
143}
144
145// Implementação de Serialize/Deserialize para avila-serde
146#[cfg(feature = "serde")]
147impl avila_serde::Serialize for Id {
148    fn to_value(&self) -> avila_serde::Value {
149        // Serializa como string hyphenated
150        avila_serde::Value::String(self.to_string())
151    }
152}
153
154#[cfg(feature = "serde")]
155impl avila_serde::Deserialize for Id {
156    fn from_value(value: avila_serde::Value) -> Result<Self, avila_serde::Error> {
157        match value {
158            avila_serde::Value::String(s) => {
159                Self::parse(&s).map_err(|e| avila_serde::Error::Parse(format!("Invalid ID: {}", e)))
160            }
161            _ => Err(avila_serde::Error::Parse("Expected string for Id".to_string()))
162        }
163    }
164}