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}