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}