randoid/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4
5use core::cell::RefCell;
6use core::fmt::{self, Write};
7
8#[cfg(all(feature = "alloc", not(feature = "std")))]
9extern crate alloc;
10#[cfg(all(feature = "alloc", not(feature = "std")))]
11use alloc::string::String;
12
13pub mod alphabet;
14mod std_rand;
15
16pub use alphabet::{Alphabet, HexAlphabet};
17use rand::Rng;
18#[cfg(feature = "std-rand")]
19pub use std_rand::*;
20
21/// Size of the buffer to store batched random data in.
22///
23/// This should be big enough to fit the step size of random data
24/// for up to 40 characters in the general case, and 64 if the alphabet has a
25/// size that is a power of 2.
26const BUFFER_SIZE: usize = 64;
27
28/// Default length of a generated id (21)
29pub const DEFAULT_SIZE: usize = 21;
30
31///
32#[derive(Clone)]
33pub struct Generator<'a, R, const N: usize = 64> {
34 alphabet: &'a Alphabet<N>,
35 random: R,
36 size: usize,
37}
38
39impl<'a, R: Rng, const N: usize> Generator<'a, R, N> {
40 /// Create a new, fully specified id generator
41 ///
42 /// Create a new generator that genartes ids composed of `size` characters chosen at random
43 /// from `alphabet`, using `random` as a source of random data.
44 ///
45 /// # Examples
46 ///
47 /// ```
48 /// use randoid::{Generator, alphabet::HEX};
49 /// # use rand::SeedableRng;
50 ///
51 /// let rand = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(0x04040404);
52 /// let mut gen = Generator::new(8, &HEX, rand);
53 /// assert_eq!(gen.gen(), "905c2761");
54 /// assert_eq!(gen.gen(), "304ec655");
55 /// ```
56 pub fn new(size: usize, alphabet: &'a Alphabet<N>, random: R) -> Self {
57 Self {
58 size,
59 alphabet,
60 random,
61 }
62 }
63
64 /// Update the size of an existing generator
65 ///
66 /// # Example
67 ///
68 /// ```
69 /// # use randoid::Generator;
70 ///
71 /// let id = Generator::default().size(32).gen();
72 /// assert_eq!(id.len(), 32);
73 /// ```
74 pub fn size(self, size: usize) -> Self {
75 Self { size, ..self }
76 }
77
78 /// Update the alphabet of an existing generator
79 ///
80 /// # Example
81 ///
82 /// ```
83 /// # use randoid::{Generator, Alphabet};
84 ///
85 /// let id = Generator::default().alphabet(&Alphabet::new(['a', 'b', 'c', 'd'])).gen();
86 /// assert!(id.chars().all(|c| matches!(c, 'a'..='d')));
87 /// ```
88 pub fn alphabet<const M: usize>(self, alphabet: &Alphabet<M>) -> Generator<'_, R, M> {
89 Generator {
90 alphabet,
91 size: self.size,
92 random: self.random,
93 }
94 }
95
96 /// Generate a new id, and write the result to `out`
97 ///
98 /// This allows you to avoid creating a new string if you would simply
99 /// be adding that string to something else.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// let mut ids = String::new();
105 ///
106 /// let mut id_gen = randoid::Generator::default();
107 /// id_gen.write_to(&mut ids);
108 /// ids.push('\n');
109 /// id_gen.write_to(&mut ids);
110 ///
111 /// assert_eq!(ids.len(), 21 * 2 + 1);
112 /// ```
113 ///
114 /// # See Also
115 /// - [`Generator::fmt`]
116 /// - [`Generator::gen`]
117 /// - [`Generator::gen_smartstring`]
118 /// - [`Generator::fmt`]
119 pub fn write_to<W: Write>(&mut self, out: &mut W) -> fmt::Result {
120 if self.size == 0 {
121 return Ok(());
122 }
123 debug_assert!(N.is_power_of_two());
124 let mask: usize = N - 1;
125 debug_assert!(mask.count_ones() == mask.trailing_ones());
126 let mut buffer = [0u8; BUFFER_SIZE];
127 let mut rem = self.size;
128 while rem > 0 {
129 let bytes = &mut buffer[..self.size.min(BUFFER_SIZE)];
130 // This generates more bits than we actually need, but using one byte per character
131 // makes the implementation a lot simpler than tracking how many bits have been used.
132 self.random.fill(bytes);
133 for &b in &*bytes {
134 let idx = b as usize & mask;
135 debug_assert!(idx < N);
136 // Since the alphabet size is a power of 2, applying the
137 // mask ensures that idx is a valid index into the alphabet.
138 out.write_char(self.alphabet.0[idx])?;
139 }
140 rem -= bytes.len();
141 }
142 Ok(())
143 }
144
145 /// Return an object which implements [`std::fmt::Display`]
146 ///
147 /// This allows you to pass a new generated id to `write!()`, `format!()`, etc.
148 /// without having to create an intermediate string.
149 ///
150 /// # Warning
151 ///
152 /// The returned object will generate a unique id, each time it's `Display`
153 /// implementation is used. It uses interior mutability in order to avoid
154 /// having to store the actual id. Similarly, the random data isn't actually
155 /// generated until it is written somewhere. In general I would advise against
156 /// using it except as a temporary to a formatting macro.
157 ///
158 /// # Examples
159 ///
160 /// ```
161 /// use randoid::Generator;
162 /// # use rand::SeedableRng;
163 ///
164 /// let mut generator = Generator::with_random(rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(1));
165 ///
166 /// println!("Your new id is: {}", generator.fmt());
167 ///
168 /// assert_eq!(format!("uid-{}", generator.fmt()), "uid-kkb3tf6ZyJm49m5J3xuB8");
169 /// let f = generator.fmt();
170 ///
171 /// assert_eq!(f.to_string(), "5jO6j5xWvMx17zY3e9NbN");
172 /// assert_eq!(f.to_string(), "kGAK7hvw7AdqTcsFNZGtr");
173 ///
174 /// ```
175 pub fn fmt(&mut self) -> Fmt<'_, 'a, R, N> {
176 Fmt(RefCell::new(self))
177 }
178
179 /// Generate a random id as a string
180 ///
181 /// # Examples
182 ///
183 /// ```
184 /// let random_id = randoid::Generator::default().gen();
185 /// ```
186 #[cfg(any(feature = "std", feature = "alloc"))]
187 pub fn gen(&mut self) -> String {
188 let mut res = String::with_capacity(self.size);
189 self.write_to(&mut res).unwrap();
190 res
191 }
192
193 /// Generate a random id as a smartstring
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// use randoid::Generator;
199 /// use smartstring::alias::String;
200 ///
201 /// let random_id: String = Generator::default().gen_smartstring();
202 /// ```
203 #[cfg(feature = "smartstring")]
204 pub fn gen_smartstring(&mut self) -> smartstring::alias::String {
205 let mut res = smartstring::alias::String::new();
206 self.write_to(&mut res).unwrap();
207 res
208 }
209}
210
211impl<'a, R: Rng> Generator<'a, R> {
212 /// Create a new randoid generator from an Rng
213 ///
214 /// Using the default size and alphabet
215 pub fn with_random(random: R) -> Self {
216 Self {
217 alphabet: &alphabet::DEFAULT,
218 random,
219 size: DEFAULT_SIZE,
220 }
221 }
222}
223
224/// See [`Generator::fmt`]
225pub struct Fmt<'g, 'a: 'g, R: Rng, const N: usize>(RefCell<&'g mut Generator<'a, R, N>>);
226
227impl<'g, 'a: 'g, R: Rng, const N: usize> fmt::Display for Fmt<'g, 'a, R, N> {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 self.0.borrow_mut().write_to(f)
230 }
231}
232
233/// Convenience macro for emulating default arguments
234///
235/// This macro takes zero to three arguments, and generates a random id as a string.
236///
237/// This simulates a function with default arguments.
238///
239/// The first argument is the length (in characters) of the generated id. Defaults to
240/// [`DEFAULT_SIZE`].
241///
242/// The second argument is the alphabet to use. This macro will automatically add borrow the
243/// alphabet, if an owned value is passed. Defaults to [`alphabet::DEFAULT`].
244///
245/// The third argument is the random number generator to use. Defaults to [`rand::thread_rng()`].
246///
247///
248/// # Examples
249///
250/// ```
251/// use randoid::randoid;
252/// use rand::SeedableRng;
253/// use rand::rngs::StdRng;
254///
255/// // Use all defaults
256/// let id = randoid!();
257/// assert_eq!(id.len(), 21);
258/// assert!(id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'));
259/// // Generate id with 32 characters, but default alphabet and rng
260/// let id = randoid!(32);
261/// assert_eq!(id.len(), 32);
262/// assert!(id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'));
263/// // Generate id with 32 hex characters, but default rng
264/// let id = randoid!(32, &randoid::alphabet::HEX);
265/// assert_eq!(id.len(), 32);
266/// assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
267/// // Generate id with 32 hex characters, using SmallRng for the RNG
268/// let id = randoid!(32, &randoid::alphabet::HEX, StdRng::from_entropy());
269/// assert_eq!(id.len(), 32);
270/// assert!(id.chars().all(|c| c.is_ascii_hexdigit()));
271///
272///
273/// ```
274#[cfg(feature = "std-rand")]
275#[macro_export]
276macro_rules! randoid {
277 () => {
278 $crate::randoid()
279 };
280 ($size:expr) => {
281 $crate::Generator::with_size($size).gen()
282 };
283 ($size:expr, &$alphabet:expr) => {
284 $crate::Generator::new($size, &$alphabet, rand::thread_rng()).gen()
285 };
286 ($size:expr, [$($alphabet:literal),+]) => {
287 randoid!($size, &$crate::alphabet::Alphabet::new([$($alphabet),+]))
288 };
289 ($size:expr, &$alphabet:expr, $rand:expr) => {
290 $crate::Generator::new($size, &$alphabet, $rand).gen()
291 };
292 ($size:expr, [$($alphabet:literal),+], $rand:expr) => {
293 randoid!($size, &$crate::alphabet::Alphabet::new([$($alphabet),+]), $rand)
294 };
295}