dtolnay / serde-yaml

Strongly typed YAML library for Rust
Apache License 2.0
964 stars 164 forks source link

Issue with `[serde(untagged)]`, parsing for KaiTai crate #223

Closed wcampbell0x2a closed 3 years ago

wcampbell0x2a commented 3 years ago

I'm a bit new to using the serde as well as the serde-yaml crates, but asking for some help with a small part of the https://doc.kaitai.io/user_guide.html parsing from yaml.

Example (rust playground link)

use serde::*;
use serde_yaml::*;
use std::collections::HashMap;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct KaiTai {
    #[serde(rename = "type")]
    t: T,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum T {
    Type(String),
    Switch(Switch),
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Switch {
    #[serde(rename = "switch-on")]
    switch_on: String,

    cases: HashMap<String, String>,
}

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

    #[test]
    fn test_fails() {
        let s = r#"type:
    switch-on: rec_type
    cases:
        1: rec_type_1
"#;

        let kaitai: KaiTai = serde_yaml::from_str(&s).unwrap();

        let expected = KaiTai {
            t: T::Switch(Switch {
                switch_on: String::from("rec_type"),
                cases: HashMap::from([(String::from("1"), String::from("rec_type_1"))]),
            }),
        };
        assert_eq!(kaitai, expected);
    }

    #[test]
    fn test_passes() {
        let s = "type: u4";

        let kaitai: KaiTai = serde_yaml::from_str(&s).unwrap();

        let expected = KaiTai {
            t: T::Type(String::from("u4")),
        };
        assert_eq!(kaitai, expected);
    }
}

Test output

running 2 tests
test tests::test_passes ... ok
test tests::test_fails ... FAILED

failures:

---- tests::test_fails stdout ----
thread 'tests::test_fails' panicked at 'called `Result::unwrap()` on an `Err` value: Message("data did not match any variant of untagged enum T", Some(Pos { marker: Marker { index: 4, line: 1, col: 4 }, path: "." }))', src/lib.rs:38:55
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    tests::test_fails

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Thanks for any help, I can't figure out how to do untagged parsers for these two different states. If there is a better place to ask this question lmk. Thanks.

jonasbb commented 3 years ago

The 1: in your yaml is deserialized as a number, so the deserialization fails when it tries to deserialize it into the cases: HashMap<String, String>,, since a number is not a String. Your example passes the tests when using a HashMap<i32, String>. The code work without the untagged, so this looks like another variants of serde-rs/serde#1183.

You could change the key type of the HashMap to something which works in both cases, for example an enum with a number and a string variant. Another option to solve this is to write custom deserialization code and use the with attribute of serde to call it.

wcampbell0x2a commented 3 years ago

The 1: in your yaml is deserialized as a number, so the deserialization fails when it tries to deserialize it into the cases: HashMap<String, String>,, since a number is not a String. Your example passes the tests when using a HashMap<i32, String>. The code work without the untagged, so this looks like another variants of serde-rs/serde#1183.

Thanks! I actually now just use the serde_yaml::Value, which correctly parses.

You could change the key type of the HashMap to something which works in both cases, for example an enum with a number and a string variant. Another option to solve this is to write custom deserialization code and use the with attribute of serde to call it.

I actually need the HashMap, since the real stuff actually has the following:

    fn test_fails() {
        let s = r#"type:
    switch-on: rec_type
    cases:
        1: rec_type_1
        2: rec_type_2
"#;

Thanks for the help!