macro_toolset/string/
rand.rs

1//! Random string.
2
3use rand::{distributions::Slice, Rng};
4
5use super::{NumStr, StringExtT, StringT};
6use crate::random::fast_random;
7
8#[macro_export]
9/// See [`RandStr`] and [`RandHexStr`] for more information.
10///
11/// # Example
12///
13/// ```
14/// use macro_toolset::{random_str, string::PushAnyT};
15/// let mut string = String::new();
16/// string.push_any(random_str!(16, b"abcABC123"));
17/// string.push_any(random_str!(HEX)); // 16 (default, max 16) * 1 (default) + 0 (default, max 16)
18/// string.push_any(random_str!(HEX: 16)); // 16 * 1 (default) + 0 (default)
19/// string.push_any(random_str!(HEX: 16, 3)); // 16 * 3 + 0 (default)
20/// string.push_any(random_str!(HEX: 16, 3, 8)); // 16 * 3 + 8
21/// ```
22///
23/// // If you like, just `to_string_ext` is fine.
24/// ```
25/// use macro_toolset::{random_str, string::StringExtT};
26/// let string = random_str!(16, b"abcABC123").to_string_ext();
27/// ```
28macro_rules! random_str {
29    ($range:expr, $charset:expr) => {{
30        $crate::string::rand::RandStr::<$range>::with_charset($charset)
31    }};
32    (HEX) => {{
33        $crate::string::rand::RandHexStr::new_default()
34    }};
35    (HEX: $l:expr) => {{
36        $crate::string::rand::RandHexStr::<$l>::new()
37    }};
38    (HEX: $l:expr, $rp:expr) => {{
39        $crate::string::rand::RandHexStr::<$l, $rp>::new()
40    }};
41    (HEX: $l:expr, $rp:expr, $lp:expr) => {{
42        $crate::string::rand::RandHexStr::<$l, $rp, $lp>::new()
43    }};
44}
45
46#[derive(Debug, Clone, Copy, Default)]
47/// Randon hex-like string, with fix length.
48///
49/// For better performance, the underlying random number is generated by
50/// xorshift algorithm then converted to hex string with [`NumStr`].
51///
52/// By default, the length is 16.
53///
54/// # Generic Parameters
55///
56/// - `L`: The length of the string. Max 16 (u64).
57/// - `RP`: Repeat `L` for `RP` times.
58/// - `LP`: Lefted length. Max 16.
59///
60/// For example, if you need a string with length 56, you may specify `L` as 16,
61/// `RP` as 56 / 16 = 3, and `LP` as 56 % 16 = 8.
62///
63/// Since `#![feature(generic_const_exprs)]` is not stable, we have to make use
64/// of these complex const generics.
65///
66/// Notice: will check if params are valid when you push this to a string, or
67/// panic in debug mode, work normally but slower in release mode.
68pub struct RandHexStr<const L: usize = 16, const RP: usize = 1, const LP: usize = 0>;
69
70impl<const L: usize, const RP: usize, const LP: usize> StringT for RandHexStr<L, RP, LP> {
71    #[inline]
72    fn encode_to_buf(self, string: &mut Vec<u8>) {
73        match L {
74            1..=16 => {
75                for _ in 0..RP {
76                    NumStr::hex_default(fast_random())
77                        .set_resize_len::<L>()
78                        .encode_to_buf(string);
79                }
80
81                if LP > 0 {
82                    debug_assert!(LP <= 16, "LP should be 0..=16");
83
84                    NumStr::hex_default(fast_random())
85                        .set_resize_len::<LP>()
86                        .encode_to_buf(string);
87                }
88            }
89            0 => {}
90            _ => {
91                #[cfg(any(debug_assertions, test))]
92                unreachable!("L should be 0..=16");
93
94                #[cfg(not(any(debug_assertions, test)))]
95                // For RELEASE mode, avoid panic but still generate random string like general
96                // RandStr does.
97                string.extend(
98                    rand::thread_rng()
99                        .sample_iter(&Slice::new(b"0123456789abcdef").unwrap())
100                        .take(L * RP + LP),
101                );
102            }
103        }
104    }
105
106    #[inline]
107    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
108        self.encode_to_buf(string);
109        string.extend(separator.as_bytes());
110    }
111
112    #[inline]
113    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
114        match L {
115            1..=16 => {
116                for _ in 0..RP {
117                    NumStr::hex_default(fast_random())
118                        .set_resize_len::<L>()
119                        .encode_to_bytes_buf(string);
120                }
121
122                if LP > 0 {
123                    debug_assert!(LP <= 16, "LP should be 0..=16");
124
125                    NumStr::hex_default(fast_random())
126                        .set_resize_len::<LP>()
127                        .encode_to_bytes_buf(string);
128                }
129            }
130            0 => {}
131            _ => {
132                #[cfg(any(debug_assertions, test))]
133                unreachable!("L should be 0..=16");
134
135                #[cfg(not(any(debug_assertions, test)))]
136                // For RELEASE mode, avoid panic but still generate random string like general
137                // RandStr does.
138                string.extend(
139                    rand::thread_rng()
140                        .sample_iter(&Slice::new(b"0123456789abcdef").unwrap())
141                        .take(L * RP + LP),
142                );
143            }
144        }
145    }
146
147    #[inline]
148    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
149        self.encode_to_bytes_buf(string);
150        string.extend(separator.as_bytes());
151    }
152}
153
154impl<const L: usize, const RP: usize, const LP: usize> StringExtT for RandHexStr<L, RP, LP> {}
155
156impl RandHexStr {
157    #[inline]
158    /// Create a new [`RandHexStr`] and generate simple random hex-like string
159    /// with length 16 (default).
160    ///
161    /// # Example
162    ///
163    /// ```rust
164    /// # use macro_toolset::string::{RandHexStr, StringExtT};
165    /// let random_str = RandHexStr::new_default().to_string_ext();
166    /// assert_eq!(random_str.len(), 16);
167    /// ```
168    pub const fn new_default() -> Self {
169        Self
170    }
171}
172
173impl<const L: usize, const RP: usize, const LP: usize> RandHexStr<L, RP, LP> {
174    #[inline]
175    /// Create a new [`RandStr`] and generate random hex-like string with
176    /// length setting by `L`, `RP`, `LP`.
177    ///
178    /// # Example
179    ///
180    /// ```rust
181    /// # use macro_toolset::string::{RandHexStr, StringExtT};
182    /// let random_str = RandHexStr::<16, 3, 8>::new().to_string_ext();
183    /// assert_eq!(random_str.len(), 56);
184    /// ```
185    pub const fn new() -> Self {
186        RandHexStr
187    }
188
189    #[inline]
190    /// Set `L`.
191    ///
192    /// You may prefer [`RandHexStr::<L, RP, LP>::new`](Self::new).
193    pub const fn with_l<const NL: usize>(self) -> RandHexStr<NL, RP, LP> {
194        RandHexStr
195    }
196
197    #[inline]
198    /// Set `RP`.
199    ///
200    /// You may prefer [`RandHexStr::<L, RP, LP>::new`](Self::new).
201    pub const fn with_rp<const NRP: usize>(self) -> RandHexStr<L, NRP, LP> {
202        RandHexStr
203    }
204
205    #[inline]
206    /// Set `LP`.
207    ///
208    /// You may prefer [`RandHexStr::<L, RP, LP>::new`](Self::new).
209    pub const fn with_lp<const NLP: usize>(self) -> RandHexStr<L, RP, NLP> {
210        RandHexStr
211    }
212}
213
214#[derive(Debug, Clone, Copy)]
215#[repr(transparent)]
216/// Randon string, with fix length and given charset.
217///
218/// # Generic Parameters
219///
220/// - `L`: The length of the string. Default is 32.
221///
222/// Notice: must make sure each u8 within the slice is valid
223/// single byte UTF-8 char.
224///
225/// If the charset is `0123456789abcdef`, [`RandHexStr`] is recommended and 4~6x
226/// faster than this (when feature `feat-random-fast` enabled).
227pub struct RandStr<'r, const L: usize = 32>(&'r [u8]);
228
229impl<const L: usize> StringT for RandStr<'_, L> {
230    #[inline]
231    fn encode_to_buf(self, string: &mut Vec<u8>) {
232        if self.0.is_empty() {
233            return;
234        }
235
236        string.extend(
237            rand::thread_rng()
238                .sample_iter(Slice::new(self.0).unwrap())
239                .take(L),
240        );
241    }
242
243    #[inline]
244    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
245        if self.0.is_empty() {
246            return;
247        }
248
249        string.extend(
250            rand::thread_rng()
251                .sample_iter(Slice::new(self.0).unwrap())
252                .take(L),
253        );
254
255        string.extend(separator.as_bytes());
256    }
257
258    #[inline]
259    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
260        if self.0.is_empty() {
261            return;
262        }
263
264        string.extend(
265            rand::thread_rng()
266                .sample_iter(Slice::new(self.0).unwrap())
267                .take(L),
268        );
269    }
270
271    #[inline]
272    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
273        if self.0.is_empty() {
274            return;
275        }
276
277        string.extend(
278            rand::thread_rng()
279                .sample_iter(Slice::new(self.0).unwrap())
280                .take(L),
281        );
282
283        string.extend(separator.as_bytes());
284    }
285}
286
287impl<const L: usize> StringExtT for RandStr<'_, L> {}
288
289impl<'r> RandStr<'r> {
290    #[inline]
291    /// Create a new [`RandStr`] and generate random string with length
292    /// setting by `L`.
293    pub const fn with_charset_default(charset: &'r [u8]) -> Self {
294        Self(charset)
295    }
296}
297
298impl<'r, const L: usize> RandStr<'r, L> {
299    #[inline]
300    /// Create a new [`RandStr`] and generate random string with length
301    /// setting by `L`.
302    pub const fn with_charset(charset: &'r [u8]) -> Self {
303        Self(charset)
304    }
305
306    #[inline]
307    /// Set `L`.
308    pub const fn with_l<const NL: usize>(self) -> RandStr<'r, NL> {
309        RandStr(self.0)
310    }
311}