dtolnay / typetag

Serde serializable and deserializable trait objects
Apache License 2.0
1.19k stars 38 forks source link

Add tests for unit structs, empty structs and enums #40

Closed rukai closed 1 year ago

rukai commented 3 years ago

Happy to trim it down if this is too much, especially the addition of serde_yaml, I needed it for my own testing but maybe we want to remove it here.

Backstory

Bit of backstory into why I wrote these tests. I was hoping to create a configuration system that deserializes yaml into various types implementing a trait. e.g.

---
ConfigItems:
- Config1:
   field1: 1
   field2: "blah"
- Config2
- Config3

However I can now see that I cant achieve this with typetag as Config2 and Config3 can not be deserialized into any rust type. The only way to get Config2 to deserialize into something would be to use Config2: {} which is fairly inelegant. So instead I will need to represent it by an enum like:

enum Config {
  Config1 { field1: i32, field2: String },
  Config2,
  Config3

These tests helped me to understand how unit and empty structs were handled and realize that using unit or empty structs with typetag wont solve my problem, hopefully they will help others discover this sooner.

rukai commented 2 years ago

Serde_yaml has msrv of 1.38. so we can either drop the serde_yaml tests or bump the msrv of typetag from 1.31 to 1.38

rukai commented 1 year ago

yep, very fair, I've removed the yaml tests

dtolnay commented 1 year ago

I can now see that I cant achieve this with typetag as Config2 and Config3 can not be deserialized into any rust type.

I am not sure what you mean by this, but the way that I would have deserialized the config in your comment is like this:

// [dependencies]
// serde = { version = "1", features = ["derive"] }
// serde_yaml = "0.9"
// typetag = "0.2"

use serde::de::{DeserializeSeed, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::Deserialize;
use std::fmt::{self, Debug};
use std::iter;

#[typetag::deserialize]
pub trait ConfigItem: Debug {}

#[derive(Deserialize, Debug)]
pub struct Config {
    #[serde(rename = "ConfigItems", deserialize_with = "vec_config_item")]
    pub items: Vec<Box<dyn ConfigItem>>,
}

#[derive(Deserialize, Debug)]
pub struct Config1 {
    pub field1: i32,
    pub field2: String,
}

#[typetag::deserialize]
impl ConfigItem for Config1 {}

#[derive(Deserialize, Debug)]
pub struct Config2;

#[typetag::deserialize]
impl ConfigItem for Config2 {}

fn vec_config_item<'de, D>(deserializer: D) -> Result<Vec<Box<dyn ConfigItem>>, D::Error>
where
    D: Deserializer<'de>,
{
    struct VecConfigItemVisitor;

    impl<'de> Visitor<'de> for VecConfigItemVisitor {
        type Value = Vec<Box<dyn ConfigItem>>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("list of ConfigItem")
        }

        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
        where
            S: SeqAccess<'de>,
        {
            let mut vec = Vec::new();
            while let Some(item) = seq.next_element_seed(ConfigItemVisitor)? {
                vec.push(item);
            }
            Ok(vec)
        }
    }

    struct ConfigItemVisitor;

    impl<'de> Visitor<'de> for ConfigItemVisitor {
        type Value = Box<dyn ConfigItem>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("ConfigItem")
        }

        fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
        where
            M: MapAccess<'de>,
        {
            let de = serde::de::value::MapAccessDeserializer::new(map);
            Deserialize::deserialize(de)
        }

        fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            let singleton_map = iter::once((string, ()));
            let de = serde::de::value::MapDeserializer::new(singleton_map);
            Deserialize::deserialize(de)
        }
    }

    impl<'de> DeserializeSeed<'de> for ConfigItemVisitor {
        type Value = Box<dyn ConfigItem>;

        fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
        where
            D: Deserializer<'de>,
        {
            deserializer.deserialize_any(self)
        }
    }

    deserializer.deserialize_seq(VecConfigItemVisitor)
}

fn main() {
    let yaml = r#"
        ConfigItems:
        - Config1:
           field1: 1
           field2: "blah"
        - Config2
    "#;
    let config: Config = serde_yaml::from_str(yaml).unwrap();
    println!("{:#?}", config);
}
rukai commented 1 year ago

Just wanted to thank you for going out of your way to provide that code sample. It managed to completely solve the problem I had!