protest_extras/generators/
domain.rs1use protest::{Generator, GeneratorConfig};
12use rand::Rng;
13use std::path::{MAIN_SEPARATOR, PathBuf};
14
15#[derive(Debug, Clone)]
23pub struct HexGenerator {
24 min_len: usize,
25 max_len: usize,
26 uppercase: bool,
27}
28
29impl HexGenerator {
30 pub fn new(min_len: usize, max_len: usize) -> Self {
32 Self {
33 min_len,
34 max_len,
35 uppercase: false,
36 }
37 }
38
39 pub fn uppercase(min_len: usize, max_len: usize) -> Self {
41 Self {
42 min_len,
43 max_len,
44 uppercase: true,
45 }
46 }
47
48 fn generate_hex_char(&self, rng: &mut dyn rand::RngCore) -> char {
49 let hex_chars = if self.uppercase {
50 b"0123456789ABCDEF"
51 } else {
52 b"0123456789abcdef"
53 };
54 hex_chars[rng.r#gen_range(0..hex_chars.len())] as char
55 }
56}
57
58impl Generator<String> for HexGenerator {
59 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
60 let len = rng.r#gen_range(self.min_len..=self.max_len);
61 (0..len).map(|_| self.generate_hex_char(rng)).collect()
62 }
63
64 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
65 let mut shrinks = Vec::new();
66
67 if !value.is_empty() && self.min_len == 0 {
69 shrinks.push(String::new());
70 }
71
72 if *value != "0".repeat(value.len()) && value.len() >= self.min_len {
74 shrinks.push("0".repeat(value.len()));
75 }
76
77 if value.len() > self.min_len {
79 shrinks.push(value.chars().take(self.min_len).collect());
80 }
81
82 if value.len() > self.min_len {
84 for i in 0..value.len().min(3) {
85 let mut chars: Vec<char> = value.chars().collect();
86 chars.remove(i);
87 let shrunk: String = chars.into_iter().collect();
88 if shrunk.len() >= self.min_len {
89 shrinks.push(shrunk);
90 }
91 }
92 }
93
94 Box::new(shrinks.into_iter())
95 }
96}
97
98#[derive(Debug, Clone)]
106pub struct Base64Generator {
107 min_bytes: usize,
108 max_bytes: usize,
109}
110
111impl Base64Generator {
112 pub fn new(min_bytes: usize, max_bytes: usize) -> Self {
117 Self {
118 min_bytes,
119 max_bytes,
120 }
121 }
122
123 fn encode_base64(bytes: &[u8]) -> String {
124 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
125
126 let mut result = String::new();
127 let mut i = 0;
128
129 while i + 2 < bytes.len() {
131 let b1 = bytes[i];
132 let b2 = bytes[i + 1];
133 let b3 = bytes[i + 2];
134
135 result.push(CHARS[(b1 >> 2) as usize] as char);
136 result.push(CHARS[(((b1 & 0x03) << 4) | (b2 >> 4)) as usize] as char);
137 result.push(CHARS[(((b2 & 0x0f) << 2) | (b3 >> 6)) as usize] as char);
138 result.push(CHARS[(b3 & 0x3f) as usize] as char);
139
140 i += 3;
141 }
142
143 if i < bytes.len() {
145 let b1 = bytes[i];
146 result.push(CHARS[(b1 >> 2) as usize] as char);
147
148 if i + 1 < bytes.len() {
149 let b2 = bytes[i + 1];
150 result.push(CHARS[(((b1 & 0x03) << 4) | (b2 >> 4)) as usize] as char);
151 result.push(CHARS[((b2 & 0x0f) << 2) as usize] as char);
152 result.push('=');
153 } else {
154 result.push(CHARS[((b1 & 0x03) << 4) as usize] as char);
155 result.push_str("==");
156 }
157 }
158
159 result
160 }
161}
162
163impl Generator<String> for Base64Generator {
164 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
165 let num_bytes = rng.r#gen_range(self.min_bytes..=self.max_bytes);
166 let bytes: Vec<u8> = (0..num_bytes).map(|_| rng.r#gen()).collect();
167 Self::encode_base64(&bytes)
168 }
169
170 fn shrink(&self, value: &String) -> Box<dyn Iterator<Item = String>> {
171 let mut shrinks = Vec::new();
172
173 if !value.is_empty() && self.min_bytes == 0 {
175 shrinks.push(String::new());
176 }
177
178 if !value.is_empty() && self.min_bytes > 0 {
180 let bytes: Vec<u8> = (0..self.min_bytes).map(|_| 0u8).collect();
181 shrinks.push(Self::encode_base64(&bytes));
182 }
183
184 let approx_bytes = (value.len() * 3) / 4;
187 if approx_bytes >= self.min_bytes {
188 let bytes = vec![0u8; approx_bytes];
189 let encoded = Self::encode_base64(&bytes);
190 if encoded != *value {
191 shrinks.push(encoded);
192 }
193 }
194
195 Box::new(shrinks.into_iter())
196 }
197}
198
199#[derive(Debug, Clone)]
207pub struct PathGenerator {
208 min_depth: usize,
209 max_depth: usize,
210 absolute: bool,
211}
212
213impl PathGenerator {
214 pub fn new(min_depth: usize, max_depth: usize) -> Self {
218 Self {
219 min_depth,
220 max_depth,
221 absolute: false,
222 }
223 }
224
225 pub fn absolute(min_depth: usize, max_depth: usize) -> Self {
227 Self {
228 min_depth,
229 max_depth,
230 absolute: true,
231 }
232 }
233
234 fn generate_component(&self, rng: &mut dyn rand::RngCore) -> String {
235 let len = rng.r#gen_range(1..=12);
236 let mut component = String::with_capacity(len);
237
238 let first_chars = b"abcdefghijklmnopqrstuvwxyz_";
240 component.push(first_chars[rng.r#gen_range(0..first_chars.len())] as char);
241
242 let chars = b"abcdefghijklmnopqrstuvwxyz0123456789_-.";
244 for _ in 1..len {
245 component.push(chars[rng.r#gen_range(0..chars.len())] as char);
246 }
247
248 component
249 }
250}
251
252impl Generator<PathBuf> for PathGenerator {
253 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> PathBuf {
254 let depth = rng.r#gen_range(self.min_depth.max(1)..=self.max_depth.max(1));
255 let mut path = PathBuf::new();
256
257 if self.absolute {
258 #[cfg(target_family = "unix")]
261 {
262 path.push(MAIN_SEPARATOR.to_string());
263 }
264 #[cfg(target_family = "windows")]
265 {
266 path.push(format!("C:{}", MAIN_SEPARATOR));
267 }
268 #[cfg(not(any(target_family = "unix", target_family = "windows")))]
269 {
270 path.push(MAIN_SEPARATOR.to_string());
271 }
272 }
273
274 for _ in 0..depth {
275 path.push(self.generate_component(rng));
276 }
277
278 path
279 }
280
281 fn shrink(&self, value: &PathBuf) -> Box<dyn Iterator<Item = PathBuf>> {
282 let mut shrinks = Vec::new();
283
284 let components: Vec<_> = value.components().collect();
285 let depth = components.len();
286
287 if depth > self.min_depth {
289 let mut path = PathBuf::new();
290 if self.absolute {
291 path.push("/");
292 }
293 for component in components.iter().take(self.min_depth.max(1)) {
294 path.push(component);
295 }
296 shrinks.push(path);
297 }
298
299 if depth > self.min_depth {
301 for i in (if self.absolute { 1 } else { 0 })..components.len().min(4) {
302 let mut path = PathBuf::new();
303 if self.absolute {
304 path.push("/");
305 }
306 for (j, component) in components.iter().enumerate() {
307 if j != i {
308 path.push(component);
309 }
310 }
311 if path.components().count() >= self.min_depth {
312 shrinks.push(path);
313 }
314 }
315 }
316
317 Box::new(shrinks.into_iter())
318 }
319}
320
321#[derive(Debug, Clone, Copy, Default)]
333pub struct UuidV4Generator;
334
335impl UuidV4Generator {
336 pub fn new() -> Self {
338 Self
339 }
340
341 fn format_uuid(bytes: [u8; 16]) -> String {
342 format!(
343 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
344 bytes[0],
345 bytes[1],
346 bytes[2],
347 bytes[3],
348 bytes[4],
349 bytes[5],
350 bytes[6],
351 bytes[7],
352 bytes[8],
353 bytes[9],
354 bytes[10],
355 bytes[11],
356 bytes[12],
357 bytes[13],
358 bytes[14],
359 bytes[15]
360 )
361 }
362}
363
364impl Generator<String> for UuidV4Generator {
365 fn generate(&self, rng: &mut dyn rand::RngCore, _config: &GeneratorConfig) -> String {
366 let mut bytes = [0u8; 16];
367 rng.fill_bytes(&mut bytes);
368
369 bytes[6] = (bytes[6] & 0x0f) | 0x40;
371
372 bytes[8] = (bytes[8] & 0x3f) | 0x80;
374
375 Self::format_uuid(bytes)
376 }
377
378 fn shrink(&self, _value: &String) -> Box<dyn Iterator<Item = String>> {
379 let shrinks = vec![
382 "00000000-0000-4000-8000-000000000000".to_string(),
383 "00000000-0000-4000-8000-000000000001".to_string(),
384 ];
385 Box::new(shrinks.into_iter())
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use rand::thread_rng;
393
394 #[test]
395 fn test_hex_generator() {
396 let generator = HexGenerator::new(8, 16);
397 let mut rng = thread_rng();
398 let config = GeneratorConfig::default();
399
400 for _ in 0..10 {
401 let hex = generator.generate(&mut rng, &config);
402 assert!(hex.len() >= 8 && hex.len() <= 16);
403 assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
404 assert!(hex.chars().all(|c| !c.is_uppercase()));
405 }
406 }
407
408 #[test]
409 fn test_hex_generator_uppercase() {
410 let generator = HexGenerator::uppercase(8, 16);
411 let mut rng = thread_rng();
412 let config = GeneratorConfig::default();
413
414 for _ in 0..10 {
415 let hex = generator.generate(&mut rng, &config);
416 assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
417 for c in hex.chars() {
419 if c.is_alphabetic() {
420 assert!(c.is_uppercase());
421 }
422 }
423 }
424 }
425
426 #[test]
427 fn test_base64_generator() {
428 let generator = Base64Generator::new(6, 32);
429 let mut rng = thread_rng();
430 let config = GeneratorConfig::default();
431
432 for _ in 0..10 {
433 let b64 = generator.generate(&mut rng, &config);
434 assert!(!b64.is_empty());
435
436 for c in b64.chars() {
438 assert!(
439 c.is_alphanumeric() || c == '+' || c == '/' || c == '=',
440 "Invalid Base64 char: {}",
441 c
442 );
443 }
444
445 if b64.contains('=') {
447 assert!(b64.ends_with('=') || b64.ends_with("=="));
448 }
449 }
450 }
451
452 #[test]
453 fn test_base64_encode() {
454 assert_eq!(Base64Generator::encode_base64(b"hello"), "aGVsbG8=");
456 assert_eq!(
457 Base64Generator::encode_base64(b"hello world"),
458 "aGVsbG8gd29ybGQ="
459 );
460 assert_eq!(Base64Generator::encode_base64(b"a"), "YQ==");
461 assert_eq!(Base64Generator::encode_base64(b"ab"), "YWI=");
462 assert_eq!(Base64Generator::encode_base64(b"abc"), "YWJj");
463 }
464
465 #[test]
466 fn test_path_generator() {
467 let generator = PathGenerator::new(1, 4);
468 let mut rng = thread_rng();
469 let config = GeneratorConfig::default();
470
471 for _ in 0..10 {
472 let path = generator.generate(&mut rng, &config);
473 let components: Vec<_> = path.components().collect();
474 assert!(!components.is_empty() && components.len() <= 4);
475
476 for component in path.iter() {
478 let s = component.to_string_lossy();
479 assert!(!s.is_empty());
480 assert!(!s.contains('/'));
481 }
482 }
483 }
484
485 #[test]
486 fn test_path_generator_absolute() {
487 let generator = PathGenerator::absolute(2, 4);
488 let mut rng = thread_rng();
489 let config = GeneratorConfig::default();
490
491 for _ in 0..10 {
492 let path = generator.generate(&mut rng, &config);
493 assert!(path.is_absolute());
494 }
495 }
496
497 #[test]
498 fn test_uuid_v4_generator() {
499 let generator = UuidV4Generator::new();
500 let mut rng = thread_rng();
501 let config = GeneratorConfig::default();
502
503 for _ in 0..10 {
504 let uuid = generator.generate(&mut rng, &config);
505
506 let parts: Vec<&str> = uuid.split('-').collect();
508 assert_eq!(parts.len(), 5);
509 assert_eq!(parts[0].len(), 8);
510 assert_eq!(parts[1].len(), 4);
511 assert_eq!(parts[2].len(), 4);
512 assert_eq!(parts[3].len(), 4);
513 assert_eq!(parts[4].len(), 12);
514
515 assert_eq!(parts[2].chars().next().unwrap(), '4');
517
518 let variant = parts[3].chars().next().unwrap();
520 assert!(
521 variant == '8' || variant == '9' || variant == 'a' || variant == 'b',
522 "Invalid variant: {}",
523 variant
524 );
525
526 for c in uuid.chars() {
528 assert!(c.is_ascii_hexdigit() || c == '-');
529 }
530 }
531 }
532
533 #[test]
534 fn test_uuid_v4_uniqueness() {
535 let generator = UuidV4Generator::new();
537 let mut rng = thread_rng();
538 let config = GeneratorConfig::default();
539
540 let mut uuids = std::collections::HashSet::new();
541 for _ in 0..100 {
542 let uuid = generator.generate(&mut rng, &config);
543 assert!(uuids.insert(uuid), "Generated duplicate UUID");
544 }
545 }
546}