Closed rukai closed 1 year 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
yep, very fair, I've removed the yaml tests
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);
}
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!
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.
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: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.