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")));
    }
}