diff options
author | Freya Murphy <freya@freyacat.org> | 2025-01-16 14:45:14 -0500 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2025-01-16 14:45:14 -0500 |
commit | 60985236c7070425c28aa0c5ce675306d06ab123 (patch) | |
tree | 7448aef5dadf19d4504151ebc370f9e20757ef10 /src/parse/de.rs | |
download | iris-60985236c7070425c28aa0c5ce675306d06ab123.tar.gz iris-60985236c7070425c28aa0c5ce675306d06ab123.tar.bz2 iris-60985236c7070425c28aa0c5ce675306d06ab123.zip |
Diffstat (limited to 'src/parse/de.rs')
-rw-r--r-- | src/parse/de.rs | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/src/parse/de.rs b/src/parse/de.rs new file mode 100644 index 0000000..4b312a4 --- /dev/null +++ b/src/parse/de.rs @@ -0,0 +1,291 @@ +//! 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<String>; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "single or list of plugin ids") + } + + fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> + where + E: de::Error, + { + self.visit_string(v.to_string()) + } + + fn visit_string<E>(self, v: String) -> Result<Self::Value, E> + where + E: de::Error, + { + Ok(vec![v]) + } + + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + 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::<String>()? { + values.push(value); + } + Ok(values) + } +} + +struct PluginIDsSeed; +impl<'de> de::DeserializeSeed<'de> for PluginIDsSeed { + type Value = HashSet<String>; + + fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error> + 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<String>); +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<E>(self, v: &str) -> Result<Self::Value, E> + where + E: de::Error, + { + self.visit_string(v.to_string()) + } + + fn visit_borrowed_str<E>(self, v: &'_ str) -> Result<Self::Value, E> + where + E: de::Error, + { + self.visit_string(v.to_string()) + } + + fn visit_string<E>(self, url: String) -> Result<Self::Value, E> + 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<A>(self, mut map: A) -> Result<Self::Value, A::Error> + 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::<String>()? { + match key.as_str() { + // plugin id + "id" => { + id = Some(map.next_value::<String>()?); + } + // repo url + "url" => { + url = Some(map.next_value::<String>()?); + } + // locked commit + "commit" => { + commit = Some(map.next_value::<String>()?); + } + // locked branch + "branch" => { + branch = Some(map.next_value::<String>()?); + } + // vim command to run on launch + "run" => { + run = Some(map.next_value::<String>()?); + } + // 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<String>); +impl<'de> de::DeserializeSeed<'de> for PluginSeed { + type Value = Plugin; + + fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error> + 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<Plugin>; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("map of plugin id's to their arguments") + } + + fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> + 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<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + 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<Plugin>; + + fn deserialize<D>(self, d: D) -> Result<Self::Value, D::Error> + 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<A>(self, mut map: A) -> Result<Self::Value, A::Error> + where + A: de::MapAccess<'de>, + { + let mut plugins = None; // list of plugins (required) + while let Some(key) = map.next_key::<String>()? { + 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: D) -> Result<Self, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_map(ConfigVisitor) + } +} + +impl Config { + pub fn parse(s: &str) -> crate::Result<Self> { + 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, Self::Err> { + Self::parse(s) + } +} |