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}