fhttp-core 1.3.1

core library for the fhttp tool
Documentation
#[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")));
    }
}