feat: add common string presets

Add token, hex, and pin helpers for the common ASCII string formats already
represented by the crate's built-in charsets. These methods keep frequent use
cases concise while continuing to route through string_from, so callers still
get the same uniform per-character sampling behavior.

Avoid adding a password preset in this change. Password generation implies
policy choices around symbols, ambiguous characters, and service-specific
constraints, while these presets are direct names for existing alphabets.

Document every preset with doctested examples and add a runnable presets
example. Update the demo to show both presets and custom string generation.

Test Plan:
- cargo test
- cargo clippy
- cargo clippy --benches
- cargo clippy --tests
- cargo +nightly fmt

Refs: IDEAS.md ergonomics backlog
This commit is contained in:
2026-04-28 20:06:39 +02:00
parent 0365ab86d1
commit 53fc84a270
3 changed files with 93 additions and 3 deletions
+70
View File
@@ -16,6 +16,7 @@
//! - Range helpers sample integer spans.
//! - Slice helpers choose caller-owned values by reference.
//! - [`charset`] contains reusable ASCII alphabets for string generation.
//! - String helpers compose those alphabets into common token formats.
use std::{
fs::File,
@@ -500,6 +501,75 @@ impl OsRandom {
}
Ok(String::from_utf8(buf).expect("ASCII bytes are valid UTF-8"))
}
/// Returns an alphanumeric ASCII token of `len` characters.
///
/// Characters are drawn uniformly from [`charset::ALPHANUMERIC`].
///
/// # 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.token(32)?;
/// assert_eq!(token.len(), 32);
/// assert!(token.bytes().all(|byte| byte.is_ascii_alphanumeric()));
/// # Ok(())
/// # }
/// ```
pub fn token(&mut self, len: usize) -> io::Result<String> {
self.string_from(charset::ALPHANUMERIC, len)
}
/// Returns a lowercase hexadecimal string of `len` characters.
///
/// Characters are drawn uniformly from [`charset::HEX_LOWER`]. The `len`
/// argument is the number of hex characters, not the number of source
/// bytes.
///
/// # 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 hex = rng.hex(16)?;
/// assert_eq!(hex.len(), 16);
/// assert!(hex.bytes().all(|byte| byte.is_ascii_hexdigit()));
/// # Ok(())
/// # }
/// ```
pub fn hex(&mut self, len: usize) -> io::Result<String> {
self.string_from(charset::HEX_LOWER, len)
}
/// Returns a decimal digit string of `len` characters.
///
/// This is useful for PINs, short human-entered codes, and other cases
/// where only ASCII digits are accepted.
///
/// # 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 pin = rng.pin(6)?;
/// assert_eq!(pin.len(), 6);
/// assert!(pin.bytes().all(|byte| byte.is_ascii_digit()));
/// # Ok(())
/// # }
/// ```
pub fn pin(&mut self, len: usize) -> io::Result<String> {
self.string_from(charset::DIGITS, len)
}
}
/// Forwards reads directly to the underlying `/dev/urandom` handle so an