#Can I use #[serde(untagged)] with an enum variant with no fields?

8 messages · Page 1 of 1 (latest)

winged forge
#

I'm writing a program that downloads files in a variety of formats, with a variety of numbers of levels of indirection. My config file is in YAML format and I'm using serde to parse it. The configuration file contains

file1:
   format1_url: "yada yada"
   key_to_extract: "blah blah"
file2:
   format2_url: "asdfghjkl"
file3: "skip"
file4:
   format3_url: "http://example.com"

My enum looks like:

#[derive(Deserialize)]
#[serde(untagged)]
enum ConfigLine {
    Format1{format1_url: String, key_to_extract: String},
    Format2{format2_url: String},
    Format3{format3_url: String},
    Skip,
}

Can I do this with a single enum? Is there a #[serde(...)] tag that I can put on Skip to accomplish this? Or would I be better off splitting it into two enums and manually implementing Deserialize on the outer one, like so:

enum ConfigLine {
    Inner(ConfigLineInner),
    Skip,
}

#[derive(Deserialize)]
enum ConfigLineInner {
    ...
}

struct ConfigLineVisitor;
impl<'de> Visitor<'de> for ConfigLineVisitor {
    fn visit_str(self, s: &str) -> Result<...> {
        if s=="skip" {
           Ok(Skip)
        } else {
           Err(E::custom("expecting config line or \"skip\""))
        }
    }
    fn visit_map(self, map: MapAccess<'de>) {
        // somehow connect `map` to ConfigLineInner's derived Deserialize implementation.
        // maybe using a fake deserializer?
    }
}
dire crown
#

?play ```rs
use serde::Deserialize;
use std::collections::HashMap;

macro_rules! named_unit_variant {
($variant:ident) => {
pub mod $variant {
pub fn serialize<S: serde::Serializer>(serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(stringify!($variant))
}

        pub fn deserialize<'de, D: serde::Deserializer<'de>>(
            deserializer: D,
        ) -> Result<(), D::Error> {
            struct V;
            impl<'de> serde::de::Visitor<'de> for V {
                type Value = ();
                fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                    f.write_str(concat!("\"", stringify!($variant), "\""))
                }
                fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
                    if value == stringify!($variant) {
                        Ok(())
                    } else {
                        Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
                    }
                }
            }
            deserializer.deserialize_str(V)
        }
    }
};

}

mod strings {
named_unit_variant!(skip);
}

fn main() {
let x = r#"file1:
format1_url: "yada yada"
key_to_extract: "blah blah"
file2:
format2_url: "asdfghjkl"
file3: "skip"
file4:
format3_url: "http://example.com""#;

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum ConfigLine {
    Format1 {
        format1_url: String,
        key_to_extract: String,
    },
    Format2 {
        format2_url: String,
    },
    Format3 {
        format3_url: String,
    },
    #[serde(with = "strings::skip")]
    Skip,
}

let y: HashMap<String, ConfigLine> = serde_yaml::from_str(x).unwrap();
dbg!(y);

}

proud hollowBOT
#
     Running `target/debug/playground`
[src/main.rs:66] y = {
    "file2": Format2 {
        format2_url: "asdfghjkl",
    },
    "file1": Format1 {
        format1_url: "yada yada",
        key_to_extract: "blah blah",
    },
    "file3": Skip,
    "file4": Format3 {
        format3_url: "http://example.com",
    },
}
dire crown
#

wow that barely fits in a normal discord message

sinful falcon
winged forge
#

Just out of curiosity, has anybody dropped that macro into a crate yet?