Testing enums and custom string_or_struct decoders.

This commit is contained in:
Bill Thiede 2021-02-07 17:09:07 -08:00
parent 33bf1dc1b3
commit 344ca0b713
3 changed files with 129 additions and 10 deletions

7
Cargo.lock generated
View File

@ -25,6 +25,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"toml", "toml",
"void",
] ]
[[package]] [[package]]
@ -69,3 +70,9 @@ name = "unicode-xid"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

View File

@ -10,3 +10,4 @@ edition = "2018"
serde = "1" serde = "1"
serde_derive = "1" serde_derive = "1"
toml = "0.5.8" toml = "0.5.8"
void = "1"

View File

@ -1,29 +1,124 @@
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::path::PathBuf;
use std::str::FromStr;
use void::Void;
#[derive(Debug, Serialize, Deserialize)] use serde::de::{self, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "storage")]
enum Storage {
S3 { bucket: String },
Filesystem { path: PathBuf },
}
#[derive(Debug, Deserialize, Serialize)]
struct Config { struct Config {
storage: Storage,
sites: HashMap<String, Site>, sites: HashMap<String, Site>,
} }
#[derive(Debug, Serialize, Deserialize)]
struct Extractor {
selector: String,
attribute: Option<String>,
}
// The `string_or_struct` function uses this impl to instantiate a `Build` if
// the input file contains a string and not a struct. According to the
// docker-compose.yml documentation, a string by itself represents a `Build`
// with just the `context` field set.
//
// > `build` can be specified either as a string containing a path to the build
// > context, or an object with the path specified under context and optionally
// > dockerfile and args.
impl FromStr for Extractor {
// This implementation of `from_str` can never fail, so use the impossible
// `Void` type as the error type.
type Err = Void;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Extractor {
selector: s.to_string(),
attribute: None,
})
}
}
fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'de> + FromStr<Err = Void>,
D: Deserializer<'de>,
{
// This is a Visitor that forwards string types to T's `FromStr` impl and
// forwards map types to T's `Deserialize` impl. The `PhantomData` is to
// keep the compiler from complaining about T being an unused generic type
// parameter. We need T in order to know the Value type for the Visitor
// impl.
struct StringOrStruct<T>(PhantomData<fn() -> T>);
impl<'de, T> Visitor<'de> for StringOrStruct<T>
where
T: Deserialize<'de> + FromStr<Err = Void>,
{
type Value = T;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or map")
}
fn visit_str<E>(self, value: &str) -> Result<T, E>
where
E: de::Error,
{
Ok(FromStr::from_str(value).unwrap())
}
fn visit_map<M>(self, map: M) -> Result<T, M::Error>
where
M: MapAccess<'de>,
{
// `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
// into a `Deserializer`, allowing it to be used as the input to T's
// `Deserialize` implementation. T then deserializes itself using
// the entries from the map visitor.
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
}
}
deserializer.deserialize_any(StringOrStruct(PhantomData))
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Site { struct Site {
url: String, url: String,
title: String, title: String,
hover: Option<String>, hover: Option<String>,
main_image: String, #[serde(deserialize_with = "string_or_struct")]
main_image: Extractor,
alternate_image: Option<String>, alternate_image: Option<String>,
} }
fn main() -> Result<(), Box<std::error::Error>> { fn main() -> Result<(), Box<std::error::Error>> {
let c = Config { let c = Config {
storage: Storage::Filesystem {
path: "/path/to/data".into(),
},
sites: vec![ sites: vec![
( (
"xkcd".to_string(), "xkcd".to_string(),
Site { Site {
url: "https://xkcd.com/1/".to_string(), url: "https://xkcd.com/1/".to_string(),
title: "title".to_string(), title: "title".to_owned(),
hover: None, hover: None,
main_image: "img".to_string(), main_image: Extractor {
selector: "img".to_owned(),
attribute: Some("src".to_owned()),
},
alternate_image: None, alternate_image: None,
}, },
), ),
@ -31,9 +126,12 @@ fn main() -> Result<(), Box<std::error::Error>> {
"sinfest".to_string(), "sinfest".to_string(),
Site { Site {
url: "https://sinfest.net/view.php?date=2000-01-17".to_string(), url: "https://sinfest.net/view.php?date=2000-01-17".to_string(),
title: "title".to_string(), title: "title".to_owned(),
hover: None, hover: None,
main_image: "img".to_string(), main_image: Extractor {
selector: "img".to_owned(),
attribute: Some("src".to_owned()),
},
alternate_image: None, alternate_image: None,
}, },
), ),
@ -41,11 +139,24 @@ fn main() -> Result<(), Box<std::error::Error>> {
.into_iter() .into_iter()
.collect(), .collect(),
}; };
println!("About to serialize");
let s = toml::to_string(&c)?; let s = toml::to_string(&c)?;
println!("s {}", s); println!("Serialized\n{}", s);
// c is inferred as the unit type judging by the error you get when you print with Display // c is inferred as the unit type judging by the error you get when you print with Display
// formatting (i.e. "{}") instead of debug formating (i.e. "{:?}"). // formatting (i.e. "{}") instead of debug formating (i.e. "{:?}").
let c = toml::from_str(&s)?; let c: Config = toml::from_str(&s)?;
println!("c {:?}", c); println!("Deserialized\n{:#?}", c);
let c: Config = toml::from_str(
r#"
[storage]
storage = "Filesystem"
path = "/path/to/data"
[sites.test]
url = "http://google.com"
title = "title"
main_image = "img"
"#,
)?;
println!("Hardcode\n{:#?}", c);
Ok(()) Ok(())
} }