1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
#[allow(unused)] use std::cell::RefCell; use regex::{Regex, Captures, Match}; use crate::{Result, FhttpError}; #[cfg(test)] thread_local!( static RANDOM_INT_CALLS: RefCell<Vec<(i32, i32)>> = RefCell::new(vec![]) ); #[cfg(not(test))] #[allow(unused)] pub fn random_int( min: i32, max: i32 ) -> i32 { use rand::{thread_rng, Rng}; thread_rng().gen_range::<i32, i32, i32>(min, max) } #[cfg(test)] #[allow(unused)] pub fn random_int( min: i32, max: i32 ) -> i32 { RANDOM_INT_CALLS.with(|c| { c.borrow_mut().push((min, max)); }); 7i32 } pub fn replace_random_ints(text: String) -> Result<String> { lazy_static! { static ref RE_ENV: Regex = Regex::new(r"(?m)\$\{randomInt\(\s*([+-]?\d+)?\s*(,\s*([+-]?\d+)\s*)?\)}").unwrap(); }; let reversed_captures: Vec<Captures> = RE_ENV.captures_iter(&text) .collect::<Vec<_>>() .into_iter() .rev() .collect(); if reversed_captures.is_empty() { Ok(text) } else { let mut buffer = text.to_owned(); for capture in reversed_captures { let group = capture.get(0).unwrap(); let (min, max) = parse_min_max( capture.get(1), capture.get(3) )?; let range = group.start()..group.end(); let value = random_int(min, max); buffer.replace_range(range, &value.to_string()); } Ok(buffer) } } fn parse_min_max( min: Option<Match>, max: Option<Match> ) -> Result<(i32, i32)> { let ret_min = min .map(|m| m.as_str().parse::<i32>()) .unwrap_or(Ok(0)) .map_err(|_| FhttpError::new( format!("min param out of bounds: {}..{}", std::i32::MIN, std::i32::MAX) ))?; let ret_max = max .map(|m| m.as_str().parse::<i32>()) .unwrap_or(Ok(std::i32::MAX)) .map_err(|_| FhttpError::new( format!("max param out of bounds: {}..{}", std::i32::MIN, std::i32::MAX) ))?; if ret_max < ret_min { Err(FhttpError::new("min cannot be greater than max")) } else { Ok((ret_min, ret_max)) } } #[cfg(test)] mod test { use super::*; use crate::{FhttpError, Result}; #[test] fn test_happy_path() -> Result<()> { let buffer = String::from("${randomInt()}"); let result = replace_random_ints(buffer)?; assert_eq!(result, "7"); let buffer = String::from("${randomInt(-5)}"); let result = replace_random_ints(buffer)?; assert_eq!(result, "7"); let buffer = String::from("${randomInt(-5, 7)}"); let result = replace_random_ints(buffer)?; assert_eq!(result, "7"); RANDOM_INT_CALLS.with(|calls| { assert_eq!(*calls.borrow(), vec![ (0, std::i32::MAX), (-5, std::i32::MAX), (-5, 7) ]); }); Ok(()) } #[test] fn test_invalid_min() { let buffer = format!("${{randomInt({})}}", std::i32::MIN as i64 - 1); let result = replace_random_ints(buffer); assert_eq!(result, Err(FhttpError::new(format!("min param out of bounds: {}..{}", std::i32::MIN, std::i32::MAX)))); } #[test] fn test_invalid_max() { let buffer = format!("${{randomInt(0, {})}}", std::i32::MAX as i64 + 1); let result = replace_random_ints(buffer); assert_eq!(result, Err(FhttpError::new(format!("max param out of bounds: {}..{}", std::i32::MIN, std::i32::MAX)))); } #[test] fn test_min_gt_max() { let buffer = "${randomInt(3, 2)}".to_owned(); let result = replace_random_ints(buffer); assert_eq!(result, Err(FhttpError::new("min cannot be greater than max"))); } }