Skip to main content

dechdev_rs/utils/
random.rs

1use rand::RngExt;
2
3/// A configuration struct for customizing random string generation.
4///
5/// This struct allows fine-grained control over which character sets are included
6/// in the generated random string, as well as options for excluding specific
7/// characters and controlling leading zero behavior.
8///
9/// # Fields
10///
11/// * `include_lowercase` - Include lowercase letters (a-z)
12/// * `include_uppercase` - Include uppercase letters (A-Z)
13/// * `include_numeric` - Include numeric digits (0-9)
14/// * `include_special_chars` - Include special characters from the `special_chars` field
15/// * `special_chars` - String containing special characters to include when `include_special_chars` is true
16/// * `exclude_custom_chars` - Whether to exclude characters specified in `custom_chars`
17/// * `custom_chars` - String containing characters to exclude when `exclude_custom_chars` is true
18/// * `is_leading_zero` - Whether to allow leading zeros in the generated string
19///
20/// # Default Values
21///
22/// By default, the struct includes lowercase, uppercase, and numeric characters,
23/// excludes special characters, and allows leading zeros. The default special
24/// characters include common symbols, and the default custom characters to exclude
25/// are visually similar characters that can be difficult to distinguish.
26///
27pub struct RandomStringCustomOptions {
28    pub include_lowercase: bool,
29    pub include_uppercase: bool,
30    pub include_numeric: bool,
31
32    pub include_special_chars: bool,
33    pub special_chars: String,
34
35    pub exclude_custom_chars: bool,
36    pub custom_chars: String,
37
38    pub is_leading_zero: bool,
39}
40
41/// Default implementation for `RandomStringCustomOptions`.
42///
43/// Creates a new instance with the following default settings:
44/// - **include_lowercase**: `true` - Includes lowercase letters (a-z)
45/// - **include_uppercase**: `true` - Includes uppercase letters (A-Z)
46/// - **include_numeric**: `true` - Includes numeric digits (0-9)
47/// - **include_special_chars**: `false` - Excludes special characters by default
48/// - **special_chars**: Contains a comprehensive set of special characters: `"!@#$%^&*()_+-={}[]:;\"'<>,.?/|~"`
49/// - **exclude_custom_chars**: `false` - Does not exclude custom characters by default
50/// - **custom_chars**: Contains characters that are visually similar or difficult to read: `"l10cCOopPsSuUvVxXwWzZ"`
51/// - **is_leading_zero**: `true` - Allows leading zeros in generated strings
52///
53/// This configuration provides a balanced approach for generating random strings that are
54/// both secure and readable, while avoiding potentially confusing character combinations.
55impl Default for RandomStringCustomOptions {
56    fn default() -> Self {
57        Self {
58            include_lowercase: true,
59            include_uppercase: true,
60            include_numeric: true,
61
62            include_special_chars: false,
63            special_chars: "!@#$%^&*()_+-={}[]:;\"'<>,.?/|~".to_string(),
64
65            exclude_custom_chars: false,
66            custom_chars: "l10cCOopPsSuUvVxXwWzZ".to_string(), // Characters that are same or difficult to read
67
68            is_leading_zero: true,
69        }
70    }
71}
72
73/// Random number of integer between min and max
74///
75/// Example Result : 43
76pub fn random_number(min: i32, max: i32) -> i32 {
77    let mut rng = rand::rng();
78    rng.random_range(min..=max)
79}
80
81/// Random number of integer as a string between min and max
82///
83/// Example Output : 43
84pub fn random_number_string(min: i32, max: i32) -> String {
85    random_number(min, max).to_string()
86}
87
88/// Generates a random string A-Z a-z 0-9
89///
90/// Example Output : bje3aMzzce1
91pub fn random_string_alpha_numeric(length: u32) -> String {
92    //A-Z a-z 0-9
93    use rand::distr::Alphanumeric;
94    let mut rng = rand::rng();
95    (0..length)
96        .map(|_| rng.sample(Alphanumeric) as char)
97        .collect()
98}
99
100/// Generates a random string 0-9
101///
102/// Example Output : 0629133926
103pub fn random_string_numeric(length: u32) -> String {
104    use rand::distr::Uniform;
105    let mut rng = rand::rng();
106    let chars: Vec<char> = "0123456789".chars().collect();
107    let uniform = Uniform::new(0, chars.len()).unwrap();
108    (0..length).map(|_| chars[rng.sample(uniform)]).collect()
109}
110
111/// Generates a random string 0-9, first character is not zero
112///
113/// Example Output : 8629133926
114pub fn random_string_number(length: u32) -> String {
115    use rand::distr::Uniform;
116    let mut rng = rand::rng();
117
118    if length == 0 {
119        return String::new();
120    }
121
122    let mut result = String::new();
123
124    // First character: 1-9
125    let first_chars: Vec<char> = "123456789".chars().collect();
126    result.push(first_chars[rng.sample(Uniform::new(0, first_chars.len()).unwrap())]);
127
128    // Remaining characters: 0-9
129    let all_chars: Vec<char> = "0123456789".chars().collect();
130    for _ in 1..length {
131        result.push(all_chars[rng.sample(Uniform::new(0, all_chars.len()).unwrap())]);
132    }
133
134    result
135}
136
137/// Generates a random string A-Z a-z
138///
139/// Example Output : bjejaMzzce
140pub fn random_string_alpha(length: u32) -> String {
141    //A-Z a-z
142    use rand::distr::Uniform;
143    let mut rng = rand::rng();
144    let chars: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
145        .chars()
146        .collect();
147    let uniform = Uniform::new(0, chars.len()).unwrap();
148    (0..length).map(|_| chars[rng.sample(uniform)]).collect()
149}
150
151/// Random string with custom options
152///
153/// Example Code :
154/// ```rust
155/// let opts = random::RandomStringCustomOptions {
156///         exclude_custom_chars: true,
157///         custom_chars: "1l0Oo".to_string(),
158///
159///         include_special_string: true,
160///         special_string: "@#()".to_string(),
161///
162///         ..Default::default()
163/// };
164///
165/// let random_string = random::random_string_custom(20, opts);
166pub fn random_string_custom(length: u32, opts: RandomStringCustomOptions) -> String {
167    use rand::distr::Uniform;
168
169    let mut rng = rand::rng();
170
171    if length == 0 {
172        return String::new();
173    }
174
175    let mut chars = String::new();
176    if opts.include_lowercase {
177        chars.push_str("abcdefghijklmnopqrstuvwxyz");
178    }
179    if opts.include_uppercase {
180        chars.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
181    }
182    if opts.include_numeric {
183        chars.push_str("0123456789");
184    }
185
186    if opts.exclude_custom_chars {
187        // Exclude difficult-to-read characters
188        chars = chars
189            .chars()
190            .filter(|c| !opts.custom_chars.contains(*c))
191            .collect();
192    }
193
194    if !opts.special_chars.is_empty() && opts.include_special_chars {
195        chars.push_str(&opts.special_chars);
196    }
197
198    if chars.is_empty() {
199        return String::new(); // Return empty string if no character set is selected
200    }
201
202    let char_vec: Vec<char> = chars.chars().collect();
203    let uniform = Uniform::new(0, char_vec.len()).unwrap();
204
205    let mut result = String::new();
206
207    // Handle first character based on is_zero_first option
208    if opts.include_numeric && !opts.is_leading_zero {
209        // First character cannot be zero
210        let non_zero_chars: Vec<char> = char_vec.iter().filter(|&&c| c != '0').copied().collect();
211        if !non_zero_chars.is_empty() {
212            let non_zero_uniform = Uniform::new(0, non_zero_chars.len()).unwrap();
213            result.push(non_zero_chars[rng.sample(non_zero_uniform)]);
214        } else {
215            result.push(char_vec[rng.sample(uniform)]);
216        }
217
218        // Remaining characters
219        for _ in 1..length {
220            result.push(char_vec[rng.sample(uniform)]);
221        }
222    } else {
223        // All characters can be any from the set
224        for _ in 0..length {
225            result.push(char_vec[rng.sample(uniform)]);
226        }
227    }
228
229    result
230}