human_friendly_ids/
distribution.rs1use rand::{Rng, distr::Distribution};
5
6use crate::{UploadId, alphabet};
7
8#[derive(Debug, Clone)]
19pub struct UploadIdDist<const N: usize>;
20
21impl<const N: usize> UploadIdDist<N> {
22 #[must_use]
24 pub const fn new() -> Self {
25 assert!(N >= 3, "ID length must be at least 3 characters");
26 Self
27 }
28}
29
30impl<const N: usize> Default for UploadIdDist<N> {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl<const N: usize> Distribution<UploadId> for UploadIdDist<N> {
37 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> UploadId {
38 debug_assert!(N >= 3, "ID length must be at least 3 characters");
39
40 let mut body = String::with_capacity(N.saturating_sub(1));
41 let mut last_char = None;
42
43 while body.len() < N.saturating_sub(1) {
44 let idx = rng.random_range(0..alphabet::GEN_ALPHABET.len());
45 #[allow(clippy::indexing_slicing, reason = "index is generated within bounds")]
46 let c = alphabet::GEN_ALPHABET[idx];
47 match (last_char, c) {
49 (Some('r'), 'n') | (Some('v'), 'v') => {}
50 (_, 'r' | 'v') if body.len() == N.saturating_sub(2) => {}
52 _ => {
53 body.push(c);
54 last_char = Some(c);
55 }
56 }
57 }
58
59 let check_char = alphabet::calculate_check_char(&body)
60 .expect("Generated body should be valid for check calculation");
61
62 UploadId(format!("{}{}", body, check_char))
63 }
64}