Skip to main content

qubit_id/uuid/
mica_uuid_like_generator.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Mica-style random UUID-like ID generation.
11//!
12//! The formatting approach follows Mica's fast UUID helper and unsigned
13//! hexadecimal formatter from [`StringUtil`], plus the related
14//! [Mica UUID benchmark notes].
15//!
16//! [`StringUtil`]: https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L335
17//! [Mica UUID benchmark notes]: https://github.com/lets-mica/mica-jmh/wiki/uuid
18
19use crate::{
20    IdError,
21    IdGenerator,
22};
23
24/// Lowercase hexadecimal digits used by the Mica UUID-like formatter.
25const HEX: &[u8; 16] = b"0123456789abcdef";
26
27/// Mask for extracting one hexadecimal digit from the low four bits.
28///
29/// A hexadecimal digit is a 4-bit nibble. After shifting the source value by a
30/// multiple of four bits, this mask keeps only the current digit. This mirrors
31/// the Java helper's `MASK = HEX_RADIX - 1` constant in Mica's
32/// `StringUtil::formatUnsignedLong`.
33const HEX_DIGIT_MASK: u128 = 0x0f;
34
35/// Mica-style UUID-like random ID generator.
36///
37/// This generator is only a random number generator that mimics the canonical
38/// UUID text shape. It produces 128 random bits and formats them as lowercase
39/// UUID-like text, but it does not rewrite RFC UUID version or variant bits.
40/// Therefore it should not be treated as a standards-compliant UUID v4
41/// generator.
42///
43/// # Origin
44/// The formatting approach is based on Mica's fast UUID utility and
45/// `formatUnsignedLong` helper:
46/// <https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L348>.
47/// The Java source also points to Mica's UUID benchmark notes:
48/// <https://github.com/lets-mica/mica-jmh/wiki/uuid>.
49#[derive(Debug, Default, Clone, Copy)]
50pub struct MicaUuidLikeGenerator;
51
52impl MicaUuidLikeGenerator {
53    /// Creates a Mica-style UUID-like generator.
54    ///
55    /// # Returns
56    /// A Mica-style UUID-like generator.
57    pub const fn new() -> Self {
58        Self
59    }
60
61    /// Formats a `u128` as canonical lowercase UUID-like text.
62    ///
63    /// # Parameters
64    /// - `value`: 128-bit ID value.
65    ///
66    /// # Returns
67    /// UUID-like text in `8-4-4-4-12` lowercase hexadecimal form.
68    pub fn format_uuid_like(value: u128) -> String {
69        let mut output = String::with_capacity(36);
70        push_hex(&mut output, value >> 96, 8);
71        output.push('-');
72        push_hex(&mut output, value >> 80, 4);
73        output.push('-');
74        push_hex(&mut output, value >> 64, 4);
75        output.push('-');
76        push_hex(&mut output, value >> 48, 4);
77        output.push('-');
78        push_hex(&mut output, value, 12);
79        output
80    }
81
82    /// Formats a `u128` as compact lowercase UUID-like text.
83    ///
84    /// # Parameters
85    /// - `value`: 128-bit ID value.
86    ///
87    /// # Returns
88    /// UUID-like text as 32 lowercase hexadecimal digits without separators.
89    pub fn format_simple_uuid_like(value: u128) -> String {
90        let mut output = String::with_capacity(32);
91        push_hex(&mut output, value, 32);
92        output
93    }
94}
95
96impl IdGenerator<u128> for MicaUuidLikeGenerator {
97    type Error = IdError;
98
99    /// Generates the next random 128-bit UUID-like value.
100    fn next_id(&self) -> Result<u128, Self::Error> {
101        let mut bytes = [0_u8; 16];
102        getrandom::fill(&mut bytes).map_err(|_| IdError::RandomSourceUnavailable)?;
103        Ok(u128::from_be_bytes(bytes))
104    }
105
106    /// Formats an ID value with canonical UUID separators.
107    fn format_id(&self, id: &u128) -> String {
108        Self::format_uuid_like(*id)
109    }
110}
111
112/// Generates a canonical lowercase UUID-like random string.
113///
114/// # Returns
115/// UUID-like text in `8-4-4-4-12` lowercase hexadecimal form.
116///
117/// # Errors
118/// Returns [`IdError::RandomSourceUnavailable`] when the operating system
119/// random source cannot fill 16 bytes.
120pub fn fast_uuid_like() -> Result<String, IdError> {
121    MicaUuidLikeGenerator::new().next_string()
122}
123
124/// Generates a compact lowercase UUID-like random string.
125///
126/// # Returns
127/// UUID-like text as 32 lowercase hexadecimal digits without separators.
128///
129/// # Errors
130/// Returns [`IdError::RandomSourceUnavailable`] when the operating system
131/// random source cannot fill 16 bytes.
132pub fn fast_simple_uuid_like() -> Result<String, IdError> {
133    let id = MicaUuidLikeGenerator::new().next_id()?;
134    Ok(MicaUuidLikeGenerator::format_simple_uuid_like(id))
135}
136
137/// Appends fixed-width lowercase hexadecimal digits to a string.
138///
139/// # Parameters
140/// - `output`: Destination string.
141/// - `value`: Source value; only the lowest `digits * 4` bits are used.
142/// - `digits`: Number of hexadecimal digits to append.
143fn push_hex(output: &mut String, value: u128, digits: usize) {
144    for index in (0..digits).rev() {
145        let nibble = ((value >> (index * 4)) & HEX_DIGIT_MASK) as usize;
146        output.push(char::from(HEX[nibble]));
147    }
148}