generic-ip 0.1.1

IP address types for generic programming
Documentation
use super::Parser;
use crate::error::{err, Error, Kind};

#[allow(clippy::inline_always)]
#[inline(always)]
pub(crate) fn parse_addr(input: &str) -> Result<u32, Error> {
    Parser::new(input)
        .take_only(Parser::take_ipv4_octets)
        .ok_or_else(|| err!(Kind::ParserError))
        .map(u32::from_be_bytes)
}

#[allow(clippy::inline_always)]
#[inline(always)]
pub(crate) fn parse_length(input: &str) -> Result<u8, Error> {
    Parser::new(input)
        .take_only(Parser::take_length)
        .ok_or_else(|| err!(Kind::ParserError))
}

#[allow(clippy::inline_always)]
#[inline(always)]
pub(crate) fn parse_prefix(input: &str) -> Result<(u32, u8), Error> {
    Parser::new(input)
        .take_with_length(Parser::take_ipv4_octets)
        .ok_or_else(|| err!(Kind::ParserError))
        .map(|(octets, len)| (u32::from_be_bytes(octets), len))
}

#[allow(clippy::inline_always)]
#[inline(always)]
pub(crate) fn parse_range(input: &str) -> Result<(u32, u8, u8, u8), Error> {
    Parser::new(input)
        .take_with_length_range(Parser::take_ipv4_octets)
        .ok_or_else(|| err!(Kind::ParserError))
        .map(|(octets, len, lower, upper)| (u32::from_be_bytes(octets), len, lower, upper))
}

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

    #[test]
    fn parse_ipv4_addr() {
        let input = "10.1.1.1";
        let addr = parse_addr(input).unwrap();
        assert_eq!(addr, 0x0a01_0101);
    }

    #[test]
    fn empty_octets() {
        let input = "...";
        let addr = parse_addr(input);
        assert!(addr.is_err());
    }

    #[test]
    fn disallow_octal() {
        let input = "1.01.0.0";
        let addr = parse_addr(input);
        #[cfg(feature = "std")]
        std::println!("{addr:?}");
        assert!(addr.is_err());
    }

    #[test]
    fn consume_all_input() {
        let input = "192.168.0.1\0";
        let addr = parse_addr(input);
        assert!(addr.is_err());
    }

    #[test]
    fn prefix_range() {
        let input = "192.0.2.0/24,25,26";
        let range = parse_range(input).unwrap();
        assert_eq!(range, (0xc000_0200, 24, 25, 26));
    }

    #[test]
    fn prefix_len() {
        let input = "/24";
        let length = parse_length(input).unwrap();
        assert_eq!(length, 24);
    }

    #[cfg(feature = "std")]
    mod proptests {
        use std::net::Ipv4Addr;
        use std::string::ToString;

        use proptest::{arbitrary::any, proptest};

        use super::*;
        use crate::concrete::Address;

        proptest! {
            #[test]
            fn parse_any_ipv4_addr(addr in any::<Ipv4Addr>()) {
                let addr_num: u32 = addr.into();
                let addr_parsed = parse_addr(&addr.to_string()).unwrap();
                assert_eq!(addr_num, addr_parsed);
            }
        }

        proptest! {
            #[test]
            fn parse_any_utf8(s in r"\PC*") {
                let stdlib: Option<Ipv4Addr> = s.parse().ok();
                assert_eq!(parse_addr(&s).map(Address::new).ok(), stdlib.map(Address::from));
            }
        }

        #[cfg(feature = "ipnet")]
        use ipnet::Ipv4Net;

        #[cfg(feature = "ipnet")]
        proptest! {
            #[test]
            fn parse_any_ipv4_prefix(addr in any::<Ipv4Addr>(), len in 0..=32u8) {
                let prefix = Ipv4Net::new(addr, len).unwrap().trunc();
                let prefix_nums = (prefix.network().into(), prefix.prefix_len());
                let prefix_parsed = parse_prefix(&prefix.to_string()).unwrap();
                assert_eq!(prefix_nums, prefix_parsed);
            }
        }
    }
}