Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the representation of Enums #717

Open
dishmaker opened this issue Feb 23, 2024 · 7 comments
Open

Change the representation of Enums #717

dishmaker opened this issue Feb 23, 2024 · 7 comments
Labels
help wanted question serde Issues related to mapping from Rust types to XML

Comments

@dishmaker
Copy link

dishmaker commented Feb 23, 2024

serde_json supports enums:

Unit	   "field": "Unit",
Newtype	   "field": { "Newtype": 42 },
Tuple      "field": [42, "answer"],
Struct     "field": { "Struct": {"q": 42, "a":"answer"} },

so quick-xml should too:

Kind	Enum field
Unit	<field>Unit</field>
Newtype	<field><Newtype>42</Newtype></field>
Tuple   <field><Tuple>42</Tuple><Tuple>answer</Tuple></field>
Struct	<field><Struct><q>42</q><a>answer</a></Struct></field>

Currently the above XML won't be generated (gives an error) while JSON would even parse back into Rust enum.
Unsupported operation: cannot serialize enum newtype variant

Serialization of enum - crate comparison:

// [package]
// name = "xml_vs_json"
// version = "0.1.0"
// edition = "2021"

// [dependencies]
// eyre = "0.6.12"
// quick-xml = { version = "0.31.0", features = ["serde", "serialize"] }
// rmp-serde = "1.1.2"
// serde = { version = "1.0.197", features = ["derive"] }
// serde_json = "1.0.114"
// serde_yaml = "0.9.32"
// toml = "0.8.10"

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MyStruct {
    pub i8_value: i8,
    pub choice_value: MyChoice,
    pub choice_list: Vec<MyChoice>,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub enum MyChoice {
    ChoiceA(MyChoiceA),
    ChoiceB(MyChoiceB),
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MyChoiceA {
    pub choice_i32: i32,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
pub struct MyChoiceB {
    pub choice_i64: i64,
    pub choice_text: String,
}

fn main() {
    let result = run();
    if let Err(err) = result {
        println!("err: {err}");
    }
}

fn run() -> eyre::Result<()> {
    let a = MyStruct {
        i8_value: 8,
        choice_value: MyChoice::ChoiceA(MyChoiceA { choice_i32: 32 }),

        choice_list: vec![
            MyChoice::ChoiceA(MyChoiceA { choice_i32: 32 }),
            MyChoice::ChoiceB(MyChoiceB {
                choice_i64: 64,
                choice_text: "my text".to_string(),
            }),
            MyChoice::ChoiceA(MyChoiceA { choice_i32: 32 }),
        ],
    };

    try_reencode_all(&a)?;

    Ok(())
}


fn try_reencode_all(a: &MyStruct) -> eyre::Result<()> {
    // pass
    let a_json = reencode_json(&a)?;
    assert_eq!(a, &a_json);

    // pass
    let a_toml = reencode_toml(&a)?;
    assert_eq!(a, &a_toml);

    // pass
    let a_yaml = reencode_yaml(&a)?;
    assert_eq!(a, &a_yaml);

    // pass
    let a_msgpack = reencode_msgpack(&a)?;
    assert_eq!(a, &a_msgpack);


    // fail: Unsupported operation: cannot serialize enum newtype variant `MyChoice::ChoiceA`
    let a_qxml = reencode_quick_xml(&a)?;
    assert_eq!(a, &a_qxml);
    Ok(())
}



fn reencode_json(input: &MyStruct) -> eyre::Result<MyStruct> {
    let json_str: String = serde_json::to_string(&input)?;
    println!("serde_json: {json_str}");
    let output = serde_json::from_str(&json_str)?;
    Ok(output)
}

fn reencode_toml(input: &MyStruct) -> eyre::Result<MyStruct> {
    let toml_str: String = toml::to_string(&input)?;
    println!("toml: {toml_str}");
    let output = toml::from_str(&toml_str)?;
    Ok(output)
}

fn reencode_yaml(input: &MyStruct) -> eyre::Result<MyStruct> {
    let yaml_str: String = serde_yaml::to_string(&input)?;
    println!("yaml: {yaml_str}");
    let output = serde_yaml::from_str(&yaml_str)?;
    Ok(output)
}

fn reencode_msgpack(input: &MyStruct) -> eyre::Result<MyStruct> {
    let msgpack_bytes: Vec<u8> = rmp_serde::to_vec(&input)?;
    println!("rmp_serde: {msgpack_bytes:?}");
    let output = rmp_serde::from_slice(&msgpack_bytes)?;
    Ok(output)
}



fn reencode_quick_xml(input: &MyStruct) -> eyre::Result<MyStruct> {
    // serialization does not work
    let qxml_str: String = quick_xml::se::to_string(&input)?;
    println!("quick_xml::se: {qxml_str}");
    let output = quick_xml::de::from_str(&qxml_str)?;
    Ok(output)
}
@Mingun
Copy link
Collaborator

Mingun commented Feb 24, 2024

You can find answer in this chapter about mapping of Rust types to XML. TL;DR: your enum variant is not a unit variant and it cannot be serialized in arbitrary-named field of struct.

@Mingun Mingun closed this as completed Feb 24, 2024
@Mingun Mingun added question serde Issues related to mapping from Rust types to XML labels Feb 24, 2024
@dishmaker
Copy link
Author

Then why does it work for toml, yaml, msgpack and der but does not for quick-xml?

Why do I have to compile different binary just for your library?
It forces me to use a feature flag in compile time.

Is there any way to show serde(rename) exclusively to this crate?

struct AnyName {
  #[serde(rename = "$value")]
  any_name: Choice,
}

@dishmaker
Copy link
Author

But still, even when using #[serde(rename = "$value")] it does not serialize
<any_name> ... </any_name> but only the inside variant.

@Mingun
Copy link
Collaborator

Mingun commented Feb 26, 2024

It is hard to answer to your questions because you do not provide your expectations. The mentioned piece of documentation shows how quick-xml performs mapping in a consistent manner. If you have concrete suggestions, please describe them and even better open a PR with them!

@dishmaker
Copy link
Author

My expectations:

Kind	In normal field
Unit	<field>Unit</field>
Newtype	<field><Newtype>42</Newtype></field>
Tuple   <field><Tuple>42</Tuple><Tuple>answer</Tuple></field>
Struct	<field><Struct><q>42</q><a>answer</a></Struct></field>

Just like JSON:

Unit	   "field": "Unit",
Newtype	   "field": { "Newtype": 42 },
Tuple      "field": [42, "answer"],
Struct     "field": { "Struct": {"q": 42, "a":"answer"} },

@dishmaker
Copy link
Author

Issue should be reopened - other crates support enum lists.
Only quick-xml is a black sheep here.

@Mingun
Copy link
Collaborator

Mingun commented Mar 21, 2024

You feel free to submit PR that would implement the desired behavior and make it in the consistent way. Probably this is possible. We also should keep the ability to use tag name as enum discriminator, because this is natural way how xs:choice in XML is represented. It must be representable with Rust enum.

@Mingun Mingun reopened this Mar 21, 2024
@Mingun Mingun changed the title Enum/choice list does not deserialize Change the representation of Enums Mar 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted question serde Issues related to mapping from Rust types to XML
Projects
None yet
Development

No branches or pull requests

2 participants