serde_dbgfmt 0.1.1

Deserialize #[derive(Debug)] output using serde
Documentation
use assert_matches::assert_matches;
use serde::Deserialize;

macro_rules! roundtrip_struct {
    {
        $(
            $( #[$attr:meta] )*
            $test:ident {
                $( #[$stattr:meta] )*
                struct $name:ident {
                    $(
                        $field:ident: $ty:ty = $value:expr
                    ),* $(,)?
                }
            }
        )*
    } => {$(
        #[test]
        $( #[$attr] )*
        fn $test() {
            #[derive(Debug, Deserialize, PartialEq)]
            $( #[$stattr] )*
            struct $name {
                $( $field: $ty, )*
            }

            let src = $name {
                $( $field: $value, )*
            };

            let text = format!("{src:?}");
            let mut de = serde_dbgfmt::Deserializer::new(&text);

            let dst: $name = serde_path_to_error::deserialize(&mut de)
                .unwrap_or_else(|e| panic!("{}", e));
            de.end().expect("failed to deserialize");

            assert_eq!(src, dst);
        }
    )*}
}

roundtrip_struct! {
    test_basic_escape_sequences {
        struct EscapeSeqs {
            tab: String = "\t".into(),
            newline: String = "\n".into(),
            carriage_return: String = "\r".into(),
            backslash: String = "\\".into(),
            single_quote: String = "\'".into(),
            double_quote: String = "\"".into(),
            null: String = "\0".into(),
        }
    }

    test_unicode_escapes {
        struct UnicodeStrs {
            ascii: String = "\u{41}".into(),
            emoji: String = "\u{1F600}".into(),
            chinese: String = "\u{4E2D}".into(),
            complex: String = "\u{1F1FA}\u{1F1F8}".into(),
        }
    }

    test_character_literals {
        struct CharLits {
            basic: char = 'A',
            tab: char = '\t',
            newline: char = '\n',
            backslash: char = '\\',
            single_quote: char = '\'',
            unicode: char = '\u{41}',
            emoji: char = '\u{1F600}',
        }
    }

    test_mixed_string_content {
        struct MixedStrs {
            normal: String = "Hello, World!".into(),
            with_escapes: String = "Line 1\nLine 2\tTabbed".into(),
            with_quotes: String = "She said \"Hello!\"".into(),
            with_unicode: String = "Café \u{2603} Unicode".into(),
        }
    }

    test_long_strings {
        struct LongStrs {
            long_text: String = "This is a very long string that contains multiple words and should test the string parsing capabilities of the deserializer with a substantial amount of text content.".into(),
            repeated: String = "A".repeat(100),
        }
    }

    test_special_characters {
        struct SpecialChars {
            symbols: String = "!@#$%^&*()_+-=[]{}|;':\",./<>?".into(),
            numbers: String = "0123456789".into(),
            whitespace: String = " \t\n\r".into(),
        }
    }

    test_multilingual_text {
        struct MultiLingual {
            english: String = "Hello".into(),
            chinese: String = "你好".into(),
            arabic: String = "مرحبا".into(),
            russian: String = "Привет".into(),
            japanese: String = "こんにちは".into(),
        }
    }
}

#[test]
fn test_empty_string() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct EmptyStr {
        value: String,
    }

    let input = r#"EmptyStr { value: "" }"#;
    let result: Result<EmptyStr, _> = serde_dbgfmt::from_str(input);

    match result {
        Ok(parsed) => {
            assert_eq!(parsed.value, "");
        }
        Err(_) => {
            eprintln!("Note: Empty strings may not be supported due to lexer limitations");
        }
    }
}

#[test]
fn test_string_with_nested_quotes() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct NestedQuotes {
        value: String,
    }

    let input = r#"NestedQuotes { value: "He said \"Hello\" to me" }"#;
    let result: Result<NestedQuotes, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    assert_eq!(result.unwrap().value, r#"He said "Hello" to me"#);
}

#[test]
fn test_raw_string_like_content() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct RawLike {
        value: String,
    }

    let input = r#"RawLike { value: "C:\\Users\\name\\file.txt" }"#;
    let result: Result<RawLike, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    assert_eq!(result.unwrap().value, r"C:\Users\name\file.txt");
}

#[test]
fn test_multiline_string_content() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct MultiLine {
        value: String,
    }

    let input = r#"MultiLine { value: "Line 1\nLine 2\nLine 3" }"#;
    let result: Result<MultiLine, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    assert_eq!(result.unwrap().value, "Line 1\nLine 2\nLine 3");
}

#[test]
fn test_unicode_codepoints() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct UnicodePoints {
        null: String,
        bmp: String,
        supplementary: String,
        emoji: String,
    }

    let input = r#"UnicodePoints { null: "\u{0}", bmp: "\u{0041}", supplementary: "\u{1D11E}", emoji: "\u{1F44D}" }"#;
    let result: Result<UnicodePoints, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.null, "\0");
    assert_eq!(parsed.bmp, "A");
    assert_eq!(parsed.supplementary, "𝄞");
    assert_eq!(parsed.emoji, "👍");
}

#[test]
fn test_char_escape_sequences() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct CharEscapes {
        tab: char,
        newline: char,
        return_char: char,
        backslash: char,
        quote: char,
        null: char,
    }

    let input = r#"CharEscapes { tab: '\t', newline: '\n', return_char: '\r', backslash: '\\', quote: '\'', null: '\0' }"#;
    let result: Result<CharEscapes, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.tab, '\t');
    assert_eq!(parsed.newline, '\n');
    assert_eq!(parsed.return_char, '\r');
    assert_eq!(parsed.backslash, '\\');
    assert_eq!(parsed.quote, '\'');
    assert_eq!(parsed.null, '\0');
}

#[test]
fn test_unicode_char_literals() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct UnicodeChars {
        ascii: char,
        accented: char,
        emoji: char,
        musical: char,
    }

    let input = r#"UnicodeChars { ascii: '\u{41}', accented: '\u{E9}', emoji: '\u{1F600}', musical: '\u{1D11E}' }"#;
    let result: Result<UnicodeChars, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.ascii, 'A');
    assert_eq!(parsed.accented, 'é');
    assert_eq!(parsed.emoji, '😀');
    assert_eq!(parsed.musical, '𝄞');
}

#[test]
fn test_boundary_unicode_values() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct BoundaryUnicode {
        min: char,
        ascii_max: char,
        bmp_max: char,
    }

    let input = r#"BoundaryUnicode { min: '\u{0}', ascii_max: '\u{7F}', bmp_max: '\u{FFFF}' }"#;
    let result: Result<BoundaryUnicode, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.min, '\0');
    assert_eq!(parsed.ascii_max, '\u{7F}');
    assert_eq!(parsed.bmp_max, '\u{FFFF}');
}

#[test]
fn test_complex_string_combinations() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct ComplexStrs {
        mixed: String,
        path_like: String,
        json_like: String,
        regex_like: String,
    }

    let input = r#"ComplexStrs { mixed: "Hello\tWorld!\nNext line with \"quotes\" and \\backslashes\\", path_like: "/usr/bin/env", json_like: "{\"key\": \"value\"}", regex_like: "\\d+\\.\\d+" }"#;
    let result: Result<ComplexStrs, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.mixed, "Hello\tWorld!\nNext line with \"quotes\" and \\backslashes\\");
    assert_eq!(parsed.path_like, "/usr/bin/env");
    assert_eq!(parsed.json_like, r#"{"key": "value"}"#);
    assert_eq!(parsed.regex_like, r"\d+\.\d+");
}

#[test]
fn test_string_with_all_whitespace_chars() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct AllWhitespace {
        spaces: String,
        tabs: String,
        newlines: String,
        mixed: String,
    }

    let input = "AllWhitespace { spaces: \"   \", tabs: \"\t\t\t\", newlines: \"\n\n\n\", mixed: \" \t\n\r\" }";
    let result: Result<AllWhitespace, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.spaces, "   ");
    assert_eq!(parsed.tabs, "\t\t\t");
    assert_eq!(parsed.newlines, "\n\n\n");
    assert_eq!(parsed.mixed, " \t\n\r");
}

#[test]
fn test_string_length_variations() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct LengthVars {
        single: String,
        short: String,
        medium: String,
    }

    let input = r#"LengthVars { single: "A", short: "Hello", medium: "This is a medium length string for testing purposes" }"#;
    let result: Result<LengthVars, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.single, "A");
    assert_eq!(parsed.short, "Hello");
    assert_eq!(parsed.medium, "This is a medium length string for testing purposes");
}

#[test]
fn test_numeric_strings() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct NumericStrs {
        integer: String,
        float: String,
        hex: String,
        scientific: String,
    }

    let input = r#"NumericStrs { integer: "12345", float: "123.456", hex: "0xABCD", scientific: "1.23e-4" }"#;
    let result: Result<NumericStrs, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.integer, "12345");
    assert_eq!(parsed.float, "123.456");
    assert_eq!(parsed.hex, "0xABCD");
    assert_eq!(parsed.scientific, "1.23e-4");
}

#[test]
fn test_option_strings() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct OptionStrs {
        some_str: Option<String>,
        none_str: Option<String>,
        some_char: Option<char>,
        none_char: Option<char>,
    }

    let input = r#"OptionStrs { some_str: Some("Hello"), none_str: None, some_char: Some('A'), none_char: None }"#;
    let result: Result<OptionStrs, _> = serde_dbgfmt::from_str(input);
    assert_matches!(result, Ok(_));
    let parsed = result.unwrap();
    assert_eq!(parsed.some_str, Some("Hello".to_string()));
    assert_eq!(parsed.none_str, None);
    assert_eq!(parsed.some_char, Some('A'));
    assert_eq!(parsed.none_char, None);
}