media-io / yaserde

Yet Another Serializer/Deserializer
MIT License
175 stars 57 forks source link

No way to serialize attributes in enums #129

Open twilight-flower opened 2 years ago

twilight-flower commented 2 years ago

There's something weird going on with enum serialization. In particular, consider this source code:

use yaserde_derive::YaSerialize;

#[derive(YaSerialize)]
struct VersionOne {
    #[yaserde(attribute)]
    attribute: String,
    #[yaserde(text)]
    body: String,
}

#[derive(YaSerialize)]
enum TestEnum {
    Version1(VersionOne),
}

#[derive(YaSerialize)]
struct TestStruct {
    #[yaserde(flatten)]
    test_enum: Vec<TestEnum>
}

fn main() {
    let test_struct = TestStruct {
        test_enum: vec![TestEnum::Version1(VersionOne {
            attribute: String::from("liquefied"),
            body: String::from("Lettuce.")
        })]
    };

    let yaserde_cfg = yaserde::ser::Config {
        perform_indent: true,
        ..Default::default()
    };

    println!("{}", yaserde::ser::to_string_with_config(&test_struct, &yaserde_cfg).unwrap())
}

The natural expected output for it would be something like this:

<?xml version="1.0" encoding="utf-8"?>
<TestStruct>
  <Version1 attribute="liquefied">Lettuce.</Version1>
</TestStruct>

...however, what it actually outputs is this:

<?xml version="1.0" encoding="utf-8"?>
<TestStruct>
  <Version1>Lettuce.</Version1>
</TestStruct>

...in other words, the attribute gets dropped somehow.

The natural workaround for this, under other circumstances, would be to use a complex enum variant, like this:

#[derive(YaSerialize)]
enum TestEnum {
    Version1 {
        #[yaserde(attribute)]
        attribute: String,
        #[yaserde(text)]
        body: String,
    }
}

...but, in fact, that will then lead to an error on attempted compilation, because the yaserde(text) derive macro doesn't work correctly for complex enum variants and throws an error instead.

So it seems like there's no way to get attribute-serialization to work properly within enums at all. Is there some workaround to this that I'm currently missing?

(This is, of course, a simplified toy example for the sake of illustrating the problem. In my actual use case, there's more than one enum variant at work; I can't just swap the enum for a standalone struct, because it's important that I be able to get multiple different enum variants into the same vec.)

TheSchemm commented 2 years ago

@LunarTulip and I have just ran into this too. I have just been messing around with the generated code from the derive macro and I think that I could make some alterations to simplify and stabilize this behavior.

luca-della-vedova commented 1 year ago

Hi!

The fix works well for the cases that were added as a test case but I believe the issue about the complex enum variant mentioned above is this present, i.e. the following struct:

  #[derive(Debug, PartialEq, YaSerialize, YaDeserialize)]
  enum Base {
    Child {
        #[yaserde(attribute)]
        val: String
    },
  }

Fails to compile

error: expected one of `.`, `;`, `?`, `}`, or an operator, found `=>`
   --> yaserde/tests/enum.rs:231:30
    |
231 |   #[derive(Debug, PartialEq, YaSerialize, YaDeserialize)]
    |                              ^^^^^^^^^^^ expected one of `.`, `;`, `?`, `}`, or an operator
    |
    = note: this error originates in the derive macro `YaSerialize` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `yaserde` (test "enum") due to previous error

I believe the statement that results in the compile error is generated in this line, since changing it to an empty quote! {} circumvents the issue (but also disables the serialization).