Skip to main content

ferrin_provider_util/
ids.rs

1//! Identifier generation.
2
3use rand::RngExt;
4
5/// Alphabet of generated ids (digits, upper- and lower-case ASCII letters).
6pub const DEFAULT_ID_ALPHABET: &str =
7    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8
9/// Default length of the random part.
10pub const DEFAULT_ID_SIZE: usize = 16;
11
12/// Produces identifiers.
13///
14/// Implement this to make ids deterministic in tests.
15pub trait IdGenerator: Send + Sync {
16    /// Returns a new identifier.
17    fn generate(&self) -> String;
18}
19
20impl<F: Fn() -> String + Send + Sync> IdGenerator for F {
21    fn generate(&self) -> String {
22        self()
23    }
24}
25
26/// Generates `prefix<separator><random>` ids from a fixed alphabet.
27#[derive(Debug, Clone)]
28pub struct PrefixedIdGenerator {
29    prefix: Option<String>,
30    separator: char,
31    size: usize,
32    alphabet: &'static str,
33}
34
35impl PrefixedIdGenerator {
36    /// Ids of the form `prefix-<size random characters>`.
37    #[must_use]
38    pub fn new(prefix: impl Into<String>, size: usize) -> Self {
39        Self {
40            prefix: Some(prefix.into()),
41            separator: '-',
42            size,
43            alphabet: DEFAULT_ID_ALPHABET,
44        }
45    }
46
47    /// Ids without a prefix.
48    #[must_use]
49    pub fn unprefixed(size: usize) -> Self {
50        Self {
51            prefix: None,
52            separator: '-',
53            size,
54            alphabet: DEFAULT_ID_ALPHABET,
55        }
56    }
57
58    /// Changes the separator between prefix and random part.
59    #[must_use]
60    pub fn with_separator(mut self, separator: char) -> Self {
61        self.separator = separator;
62        self
63    }
64
65    /// The prefix, if any.
66    #[must_use]
67    pub fn prefix(&self) -> Option<&str> {
68        self.prefix.as_deref()
69    }
70}
71
72impl Default for PrefixedIdGenerator {
73    fn default() -> Self {
74        Self::unprefixed(DEFAULT_ID_SIZE)
75    }
76}
77
78impl IdGenerator for PrefixedIdGenerator {
79    fn generate(&self) -> String {
80        let random = random_string(self.size, self.alphabet);
81        match &self.prefix {
82            Some(prefix) => format!("{prefix}{}{random}", self.separator),
83            None => random,
84        }
85    }
86}
87
88/// Generates a random 16-character id.
89#[must_use]
90pub fn generate_id() -> String {
91    random_string(DEFAULT_ID_SIZE, DEFAULT_ID_ALPHABET)
92}
93
94fn random_string(size: usize, alphabet: &str) -> String {
95    let chars: Vec<char> = alphabet.chars().collect();
96    let mut rng = rand::rng();
97    (0..size)
98        .map(|_| chars[rng.random_range(0..chars.len())])
99        .collect()
100}