oha 0.6.5

Ohayou(おはよう), HTTP load generator, inspired by rakyll/hey with tui animation.
use std::{borrow::Cow, string::FromUtf8Error};

use rand::prelude::*;
use rand_regex::Regex;
use thiserror::Error;
use url::{ParseError, Url};

#[derive(Clone, Debug)]
pub enum UrlGenerator {
    Static(Url),
    Dynamic(Regex),
}

#[derive(Error, Debug)]
pub enum UrlGeneratorError {
    #[error("{0}, generated url: {1}")]
    ParseError(ParseError, String),
    #[error(transparent)]
    FromUtf8Error(#[from] FromUtf8Error),
}

impl UrlGenerator {
    pub fn new_static(url: Url) -> Self {
        Self::Static(url)
    }

    pub fn new_dynamic(regex: Regex) -> Self {
        Self::Dynamic(regex)
    }

    pub fn generate<R: Rng>(&self, rng: &mut R) -> Result<Cow<Url>, UrlGeneratorError> {
        match self {
            Self::Static(url) => Ok(Cow::Borrowed(url)),
            Self::Dynamic(regex) => {
                let generated = Distribution::<Result<String, FromUtf8Error>>::sample(regex, rng)?;
                Ok(Cow::Owned(Url::parse(generated.as_str()).map_err(|e| {
                    UrlGeneratorError::ParseError(e, generated)
                })?))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand_regex::Regex as RandRegex;
    use regex::Regex;
    use std::net::Ipv4Addr;
    use url::{Host, Url};

    #[test]
    fn test_url_generator_static() {
        let url_generator = UrlGenerator::new_static(Url::parse("http://127.0.0.1/test").unwrap());
        let url = url_generator.generate(&mut thread_rng()).unwrap();
        assert_eq!(url.host(), Some(Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1))));
        assert_eq!(url.path(), "/test");
    }

    #[test]
    fn test_url_generator_dynamic() {
        let path_regex = "/[a-z][a-z][0-9]";
        let url_generator = UrlGenerator::new_dynamic(
            RandRegex::compile(&format!(r"http://127\.0\.0\.1{}", path_regex), 4).unwrap(),
        );
        let url = url_generator.generate(&mut thread_rng()).unwrap();
        assert_eq!(url.host(), Some(Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1))));
        assert!(Regex::new(path_regex)
            .unwrap()
            .captures(url.path())
            .is_some());
    }
}