rnp 0.1.146

A simple layer 4 ping tool for cloud.
Documentation
use num::One;
use std::fmt;
use std::iter::Sum;
use std::ops::{Add, RangeInclusive, Sub};
use std::str::FromStr;

#[derive(Debug, Clone, PartialEq)]
pub struct RangeListInclusive<Idx> {
    pub ranges: Vec<RangeInclusive<Idx>>,
}

impl<Idx: Copy + PartialOrd<Idx> + Add<Output = Idx> + Sub<Output = Idx> + One + Sum> RangeListInclusive<Idx> {
    pub fn calculate_total_port_count(&self) -> Idx {
        return self.ranges.iter().map(|r| *r.end() - *r.start() + One::one()).sum();
    }
}

impl<Idx: Copy + FromStr> FromStr for RangeListInclusive<Idx> {
    type Err = String;

    fn from_str(input: &str) -> Result<RangeListInclusive<Idx>, Self::Err> {
        let mut parsed_ranges = Vec::new();

        if input.len() == 0 {
            return Ok(RangeListInclusive { ranges: parsed_ranges });
        }

        for input_part in input.split(",") {
            let range_parts = input_part.split("-").collect::<Vec<&str>>();
            if range_parts.len() == 1 {
                let port = Idx::from_str(range_parts[0]).map_err(|_| format!("Parse port \"{}\" failed.", range_parts[0]))?;

                parsed_ranges.push(port..=port);
            } else if range_parts.len() == 2 {
                let port_start = Idx::from_str(range_parts[0]).map_err(|_| format!("Parse port range start \"{}\" failed.", range_parts[0]))?;
                let port_end = Idx::from_str(range_parts[1]).map_err(|_| format!("Parse port range end \"{}\" failed.", range_parts[1]))?;

                parsed_ranges.push(port_start..=port_end);
            } else {
                return Err(format!(
                    "Invalid port range \"{}\". Each port range should only contain 1 or 2 elements. Examples: 1024, 10000-11000",
                    input_part
                ));
            }
        }

        return Ok(RangeListInclusive { ranges: parsed_ranges });
    }
}

impl<Idx: fmt::Display + PartialEq> fmt::Display for RangeListInclusive<Idx> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut is_first_range = true;
        for range in &self.ranges {
            if is_first_range {
                is_first_range = false;
            } else {
                write!(f, ",")?;
            }

            if range.start() == range.end() {
                write!(f, "{}", range.start())?;
            } else {
                write!(f, "{}-{}", range.start(), range.end())?;
            }
        }

        Ok(())
    }
}

pub type PortRangeList = RangeListInclusive<u16>;

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn parsing_range_list_should_work() {
        assert_eq!(RangeListInclusive { ranges: vec![] }, "".parse::<RangeListInclusive<i32>>().unwrap());
        assert_eq!(RangeListInclusive { ranges: vec![(1..=1)] }, "1".parse::<RangeListInclusive<i32>>().unwrap());
        assert_eq!(RangeListInclusive { ranges: vec![(1..=1), (2..=2)] }, "1,2".parse::<RangeListInclusive<i32>>().unwrap());
        assert_eq!(RangeListInclusive { ranges: vec![(1..=2)] }, "1-2".parse::<RangeListInclusive<i32>>().unwrap());
        assert_eq!(RangeListInclusive { ranges: vec![(1..=2), (5..=6)] }, "1-2,5-6".parse::<RangeListInclusive<i32>>().unwrap());
        assert_eq!(RangeListInclusive { ranges: vec![(1..=1), (2..=2), (5..=6)] }, "1,2,5-6".parse::<RangeListInclusive<i32>>().unwrap());
        assert_eq!(
            RangeListInclusive { ranges: vec![(1..=1), (2..=2), (5..=6), (100..=200)] },
            "1,2,5-6,100-200".parse::<RangeListInclusive<i32>>().unwrap()
        );
    }

    #[test]
    fn parsing_invalid_range_list_should_fail() {
        assert!("1-".parse::<RangeListInclusive<i32>>().is_err());
        assert!("-2".parse::<RangeListInclusive<i32>>().is_err());
        assert!("1,2,5-".parse::<RangeListInclusive<i32>>().is_err());
        assert!("-2".parse::<RangeListInclusive<i32>>().is_err());
    }

    #[test]
    fn range_list_to_string_should_work() {
        assert_eq!("", RangeListInclusive { ranges: Vec::<RangeInclusive<i32>>::new() }.to_string());
        assert_eq!("1", RangeListInclusive { ranges: vec![(1..=1)] }.to_string());
        assert_eq!("1,2", RangeListInclusive { ranges: vec![(1..=1), (2..=2)] }.to_string());
        assert_eq!("1-2", RangeListInclusive { ranges: vec![(1..=2)] }.to_string());
        assert_eq!("1-2,5-6", RangeListInclusive { ranges: vec![(1..=2), (5..=6)] }.to_string());
        assert_eq!("1,2,5-6", RangeListInclusive { ranges: vec![(1..=1), (2..=2), (5..=6)] }.to_string());
        assert_eq!("1,2,5-6,100-200", RangeListInclusive { ranges: vec![(1..=1), (2..=2), (5..=6), (100..=200)] }.to_string());
    }
}