serde-rs / serde

Serialization framework for Rust
https://serde.rs/
Apache License 2.0
9.03k stars 763 forks source link

`#![serde(deny_unknown_fields)]` does not work as expected on unit variants of tagged enum #2294

Open Skgland opened 1 year ago

Skgland commented 1 year ago

I would expect deny_unknown_fields to work with unit variants of tagged enums such that in the following example, both test pass.

Instead the test using the NewEnumUsingUnit enum fails as the token field is ignored unexpectedly.

Edit: Updated Example after https://github.com/serde-rs/serde/issues/2294#issuecomment-1407182878

#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "type", deny_unknown_fields)]
enum OldEnum {
    A { token: String },
    B,
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "type", deny_unknown_fields)]
enum NewEnumUsingUnit {
    A,
    B,
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(tag = "type", deny_unknown_fields)]
enum NewEnumUsingEmptyStruct {
    A {},
    B,
}

#[test]
fn test_enum_migration_with_unit_variant() {
    let old = OldEnum::A {
        token: String::from("testToken"),
    };
    let serialized = toml::ser::to_string_pretty(&old).unwrap();
    println!("{}", serialized);
    toml::de::from_str::<'_, NewEnumUsingUnit>(&serialized)
        .expect_err("old has a field (token) new does not know and should deny");
}

#[test]
fn test_enum_migration_with_struct_variant() {
    let old = OldEnum::A {
        token: String::from("testToken"),
    };
    let serialized = toml::ser::to_string_pretty(&old).unwrap();
    println!("{}", serialized);
    toml::de::from_str::<'_, NewEnumUsingEmptyStruct>(&serialized)
        .expect_err("old has a field (token) new does not know and should deny");
}

play-ground

kangalio commented 1 year ago

Smaller repro:

#[derive(serde::Deserialize)]
#[serde(tag = "type", deny_unknown_fields)]
enum Enum {
    A,
    B,
}

fn main() {
    serde_json::from_str::<Enum>(r#"{"type": "A", "token": "testToken"}"#).unwrap();
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6ca35eee70212b03ba56a728757e1202

uint commented 1 year ago

I'm building a JSON Typedef derive lib and stumbled into this. Any updates? I should treat this as a bug and not expected behavior, right?

anp commented 1 year ago

I hit this as well when changing a variant from no payload to an empty struct payload (for reasons), here's an interesting repro:

#[derive(Debug, serde::Deserialize)]
#[serde(tag = "type", deny_unknown_fields)]
enum EnumBareVariant {
    A,
}

#[derive(Debug, serde::Deserialize)]
#[serde(tag = "type", deny_unknown_fields)]
enum EnumEmptyStructVariant {
    A {},
}

fn main() {
    let with_unknown_field = r#"{"type": "A", "token": "testToken"}"#;
    serde_json::from_str::<EnumEmptyStructVariant>(with_unknown_field).expect_err("this passes");
    serde_json::from_str::<EnumBareVariant>(with_unknown_field).expect_err("this fails");
}

It seems that this gap is specific to having a bare enum variant.