ircv3_parse 4.0.0

Zero-copy parser for IRCv3 messages
Documentation
mod components;

use components::{
    commands::invalid_command_strategy, escape::escaped_strategy, params::ParamsType, TestMessage,
};
use ircv3_parse::{unescape, validators};
use proptest::{prelude::any, prop_assert, prop_assert_eq, proptest, test_runner::Config};

proptest! {
    #![proptest_config(Config::with_cases(3000))]

    #[test]
    fn base(
        test_message in any::<TestMessage>()
    ){
        let msg = test_message.to_string();
        let TestMessage {tags, source, command, params, line_ending} = test_message;
        let result = ircv3_parse::parse(&msg).unwrap();


        if let Some(tags) = tags {
            let actual_tags = result.tags();

            prop_assert!(actual_tags.is_some());
            let actual_tags = actual_tags.unwrap();

            prop_assert!(actual_tags.validate().is_ok());
            prop_assert_eq!(tags.to_string(), actual_tags.to_string());
            prop_assert_eq!(tags.count(), actual_tags.count());


            for tag in tags.iter(){
                let expected_key = tag.expected_key();
                prop_assert!(validators::tag_key(&expected_key).is_ok());

                let actual_value = actual_tags.get(&expected_key);
                if let Some(value) = actual_value {
                    prop_assert!(validators::tag_value(value.as_str()).is_ok());
                }

                prop_assert_eq!(tag.expected_value(), actual_tags.get(&expected_key).map(|x|x.to_string()));
            }
        }

        if let Some(expected_source) = source {
            let actual_source = result.source();

            prop_assert!(actual_source.is_some());
            let actual_source = result.source().unwrap();

            prop_assert!(actual_source.validate().is_ok());
            prop_assert_eq!(expected_source.to_string(), actual_source.to_string());
            prop_assert_eq!(expected_source.expected_name(), actual_source.name);
            prop_assert_eq!(expected_source.expected_user(), actual_source.user);
            prop_assert_eq!(expected_source.expected_host(), actual_source.host);
        }

        let cmd = result.command();
        prop_assert!(cmd.validate().is_ok());
        prop_assert_eq!(command, cmd.as_str());

        let actual_params = result.params();
        prop_assert_eq!(params.raw(), actual_params.to_string());

        match params {
            ParamsType::MiddlesOnly(middles) => {
                prop_assert!(actual_params.middles.validate().is_ok());
                prop_assert_eq!(middles.len(), actual_params.middles.count());
                prop_assert_eq!(middles, actual_params.middles.to_vec());

                prop_assert!(actual_params.trailing.is_none());
            },
            ParamsType::MiddlesTrailing(middles, trailing) => {
                prop_assert!(actual_params.middles.validate().is_ok());
                prop_assert_eq!(middles.len(), actual_params.middles.count());
                prop_assert_eq!(middles, actual_params.middles.to_vec());

                prop_assert!(actual_params.trailing.is_some());
                prop_assert_eq!(trailing, actual_params.trailing.to_string());
            },
            ParamsType::TrailingOnly(trailing) => {
                prop_assert_eq!(0, actual_params.middles.count());

                prop_assert!(actual_params.trailing.is_some());
                prop_assert_eq!(trailing, actual_params.trailing.to_string());

            },
            ParamsType::None => {
                prop_assert_eq!(0, actual_params.middles.count());
                prop_assert!(actual_params.trailing.is_none());
            },
        };

        let round = format!("{}{}{}{}{}",
            if let Some(t)=result.tags(){
               format!("@{} ",t)
            }else {
                "".to_string()
            },
            if let Some(s) = result.source() {
                format!(":{} ",s)
            } else {
                "".to_string()
            },
            result.command(),
            result.params().message(),
            line_ending
            );
        prop_assert_eq!(msg, round);
    }
}

#[test]
fn test_basic_unescaping() {
    assert_eq!(unescape("hello\\sworld"), "hello world");
    assert_eq!(unescape("semi\\:colon"), "semi;colon");
    assert_eq!(unescape("back\\\\slash"), "back\\slash");
    assert_eq!(unescape("carriage\\rreturn"), "carriage\rreturn");
    assert_eq!(unescape("line\\nfeed"), "line\nfeed");
}

#[test]
fn test_edge_cases() {
    assert_eq!(unescape(""), "");
    assert_eq!(unescape("\\"), "\\");
    assert_eq!(unescape("no_escapes"), "no_escapes");
    assert_eq!(unescape("\\unknown"), "\\unknown");
    assert_eq!(unescape("multiple\\s\\:escapes"), "multiple ;escapes");
}

proptest! {
    #![proptest_config(Config::with_cases(3000))]
    #[test]
    fn escaped_string(
        (input, expected) in escaped_strategy()
    ) {
        prop_assert_eq!(expected, unescape(&input));
    }
}

proptest! {
    #![proptest_config(Config::with_cases(3000))]
    #[test]
    fn invalid_command(
        cmd in invalid_command_strategy()
    ) {
        prop_assert!(validators::command(&cmd).is_err());
    }
}