//! Deserialize an iris structure from a string. use crate::{Config, Plugin}; use serde::de; use std::collections::HashSet; use std::fmt; use std::str::FromStr; /// Errors that can occur when deserializing a type #[derive(thiserror::Error, Debug)] pub enum Error { /// Occurs when toml could not be deserialized #[error(transparent)] Toml(#[from] toml::de::Error), } macro_rules! error { ($($arg:tt)*) => { de::Error::custom(format!($($arg)*)) }; } struct PluginIDsVisitor; impl<'de> de::Visitor<'de> for PluginIDsVisitor { type Value = Vec; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "single or list of plugin ids") } fn visit_str(self, v: &str) -> Result where E: de::Error, { self.visit_string(v.to_string()) } fn visit_string(self, v: String) -> Result where E: de::Error, { Ok(vec![v]) } fn visit_seq(self, mut seq: A) -> Result where A: de::SeqAccess<'de>, { let mut values = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity); while let Some(value) = seq.next_element::()? { values.push(value); } Ok(values) } } struct PluginIDsSeed; impl<'de> de::DeserializeSeed<'de> for PluginIDsSeed { type Value = HashSet; fn deserialize(self, d: D) -> Result where D: de::Deserializer<'de>, { let values = d.deserialize_any(PluginIDsVisitor)?; Ok(HashSet::from_iter(values)) } } // plugin visitor with seeded plugin id struct PluginVisitor(Option); impl<'de> de::Visitor<'de> for PluginVisitor { type Value = Plugin; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.0 { Some(id) => write!(f, "arguments for plugin '{id}'"), None => write!(f, "plugin id and arguments"), } } fn visit_str(self, v: &str) -> Result where E: de::Error, { self.visit_string(v.to_string()) } fn visit_borrowed_str(self, v: &'_ str) -> Result where E: de::Error, { self.visit_string(v.to_string()) } fn visit_string(self, url: String) -> Result where E: de::Error, { let Some(id) = self.0 else { return Err(de::Error::missing_field("id")); }; Self::Value::new(id, &url).map_err(E::custom) } fn visit_map(self, mut map: A) -> Result where A: de::MapAccess<'de>, { // parse each map value as a possbile field for the plugin let mut id = self.0; // plugin id (required) let mut url = None; // repository url (required) let mut commit = None; // commit to lock to let mut branch = None; // branch to lock to let mut run = None; // command to run on launch let mut before = HashSet::new(); // plugins to load before let mut after = HashSet::new(); // plugins to load after while let Some(key) = map.next_key::()? { match key.as_str() { // plugin id "id" => { id = Some(map.next_value::()?); } // repo url "url" => { url = Some(map.next_value::()?); } // locked commit "commit" => { commit = Some(map.next_value::()?); } // locked branch "branch" => { branch = Some(map.next_value::()?); } // vim command to run on launch "run" => { run = Some(map.next_value::()?); } // plugins to load before "before" => { before = map.next_value_seed(PluginIDsSeed)?; } // plugins to load after "after" => { after = map.next_value_seed(PluginIDsSeed)?; } // invalid key! key => return Err(error!("unknown plugin field '{key}'")), }; } // id is a required field let Some(id) = id else { return Err(de::Error::missing_field("id")); }; // url is a required field let Some(url) = url else { return Err(de::Error::missing_field("url")); }; let mut plugin = Self::Value::new(id, &url).map_err(de::Error::custom)?; plugin.commit = commit; plugin.branch = branch; plugin.run = run; plugin.before = before; plugin.after = after; Ok(plugin) } } // deserialize plugin with possible id struct PluginSeed(Option); impl<'de> de::DeserializeSeed<'de> for PluginSeed { type Value = Plugin; fn deserialize(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_any(PluginVisitor(self.0)) } } // plugins visitor struct PluginsVisitor; impl<'de> de::Visitor<'de> for PluginsVisitor { type Value = Vec; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("map of plugin id's to their arguments") } fn visit_map(self, mut map: A) -> Result where A: de::MapAccess<'de>, { let mut plugins = map.size_hint().map_or_else(Vec::new, Vec::with_capacity); while let Some(id) = map.next_key()? { let plugin = map.next_value_seed(PluginSeed(Some(id)))?; plugins.push(plugin); } Ok(plugins) } fn visit_seq(self, mut seq: A) -> Result where A: de::SeqAccess<'de>, { let mut plugins = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity); while let Some(plugin) = seq.next_element_seed(PluginSeed(None))? { plugins.push(plugin); } Ok(plugins) } } // plugins seed struct PluginsSeed; impl<'de> de::DeserializeSeed<'de> for PluginsSeed { type Value = Vec; fn deserialize(self, d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_any(PluginsVisitor) } } // config visitor struct ConfigVisitor; impl<'de> de::Visitor<'de> for ConfigVisitor { type Value = Config; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("iris config value") } fn visit_map(self, mut map: A) -> Result where A: de::MapAccess<'de>, { let mut plugins = None; // list of plugins (required) while let Some(key) = map.next_key::()? { match key.as_str() { "plugins" => { plugins = Some(map.next_value_seed(PluginsSeed)?); } key => return Err(error!("unknown config field '{key}'")), }; } // plugins is a required field let Some(mut plugins) = plugins else { return Err(de::Error::missing_field("plugins")); }; plugins.sort(); Ok(Config { plugins }) } } impl<'de> de::Deserialize<'de> for Config { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { d.deserialize_map(ConfigVisitor) } } impl Config { pub fn parse(s: &str) -> crate::Result { toml::from_str(s) .map_err(Error::from) .map_err(crate::Error::from) } } impl FromStr for Config { type Err = crate::Error; fn from_str(s: &str) -> Result { Self::parse(s) } }