diff --git a/examples/choices.rs b/examples/choices.rs new file mode 100644 index 0000000..60b4e72 --- /dev/null +++ b/examples/choices.rs @@ -0,0 +1,16 @@ +//! Run with: `cargo run --example choices` + +use ez_urandom::{OsRandom, charset}; + +fn main() -> std::io::Result<()> { + let mut rng = OsRandom::try_new()?; + + let environments = ["dev", "staging", "prod"]; + let environment = rng.choose(&environments)?; + let suffix = rng.pick(charset::HEX_LOWER)?; + + println!("environment: {environment}"); + println!("hex suffix : {}", char::from(suffix)); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 4e5848f..8bbad4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ //! - Convenience byte readers fill caller-provided buffers. //! - Sampling helpers build unbiased higher-level choices from those primitive //! readers. +//! - Range helpers sample integer spans. +//! - Slice helpers choose caller-owned values by reference. //! - [`charset`] contains reusable ASCII alphabets for string generation. use std::{ @@ -423,16 +425,51 @@ impl OsRandom { /// Returns a uniformly chosen byte from `set`. /// /// # Panics - /// Panics if `set` is empty or longer than `u32::MAX`. + /// Panics if `set` is empty. /// /// # Errors /// Returns any I/O error produced while reading from `/dev/urandom`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut rng = ez_urandom::OsRandom::try_new()?; + /// let digit = rng.pick(ez_urandom::charset::DIGITS)?; + /// assert!(digit.is_ascii_digit()); + /// # Ok(()) + /// # } + /// ``` pub fn pick(&mut self, set: &[u8]) -> io::Result { - assert!(!set.is_empty(), "set must not be empty"); - let n = u32::try_from(set.len()).expect("set must fit in u32"); - let i = usize::try_from(self.gen_range_u32(n)?) - .expect("u32 fits in usize on supported platforms"); - Ok(set[i]) + Ok(*self.choose(set)?) + } + + /// Returns a uniformly chosen item from `items`. + /// + /// The returned reference points into the caller-provided slice; no item is + /// cloned or moved. + /// + /// # Panics + /// Panics if `items` is empty. + /// + /// # Errors + /// Returns any I/O error produced while reading from `/dev/urandom`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut rng = ez_urandom::OsRandom::try_new()?; + /// let colors = ["red", "green", "blue"]; + /// let color = rng.choose(&colors)?; + /// assert!(colors.contains(color)); + /// # Ok(()) + /// # } + /// ``` + pub fn choose<'a, T>(&mut self, items: &'a [T]) -> io::Result<&'a T> { + assert!(!items.is_empty(), "items must not be empty"); + let i = self.gen_range_usize(items.len())?; + Ok(&items[i]) } /// Returns a `String` of `len` characters drawn uniformly from `set`. @@ -443,6 +480,18 @@ impl OsRandom { /// /// # Errors /// Returns any I/O error produced while reading from `/dev/urandom`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut rng = ez_urandom::OsRandom::try_new()?; + /// let token = rng.string_from(ez_urandom::charset::ALPHANUMERIC, 24)?; + /// assert_eq!(token.len(), 24); + /// assert!(token.is_ascii()); + /// # Ok(()) + /// # } + /// ``` pub fn string_from(&mut self, set: &[u8], len: usize) -> io::Result { assert!(set.is_ascii(), "set must contain only ASCII bytes"); let mut buf = vec![0u8; len];