diff --git a/src/settings/mod.rs b/src/settings/mod.rs index a87f883..7846c63 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -4,7 +4,7 @@ extern crate serde_yaml; use serde::{Deserialize, Serialize}; extern crate log; -use log::{info, warn}; +use log::{error, info}; // mod utils; pub mod utils; @@ -15,6 +15,80 @@ use utils::SettingsType; /// use. pub const RULESET_CURRENT_VERSION: i32 = 1; +/// Encode a [`Settings`] struct to a filetype, returns a +/// `std::result::Result` +/// +/// # Arguments +/// +/// * `funcrate` - `serde`-compatible crate to use, must implement `to_string` +/// * `content` - content to encode, must be `Settings` struct +/// +/// # Example +/// +/// ```ignore +/// # use lang_evolve_core::settings::*; +/// # use lang_evolve_core::encode_settings; +/// use std::io::{Error, ErrorKind}; +/// use std::path::Path; +/// let filetype = utils::get_file_type(Path::new("./path/to/file.json")); +/// let s = Settings::new(); +/// let content = match filetype { +/// utils::SettingsType::Yaml => encode_settings!(serde_yaml, &s).unwrap(), +/// utils::SettingsType::Json => encode_settings!(serde_json, &s).unwrap(), +/// _ => panic!("Could not encode settings"), +/// }; +/// ``` +/// +/// [`Settings`]: ./settings/struct.Settings.html +#[macro_export(local_inner_macros)] +macro_rules! encode_settings { + ($funcrate:ident, $content:expr) => { + match $funcrate::to_string($content) { + Err(e) => { + log::error!("Could not serialize settings: {}", e.to_string()); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + e, + )); + } + Ok(val) => val, + } + }; +} + +/// Decode a [`Settings`] struct from a `std::std::String`, returns a +/// std::result::Result +/// +/// # Arguments +/// +/// * `funcrate` - `serde`-compatible crate to use, mus implement `from_string` +/// * `content` - `&str` to decode into a [`Settings`] +/// +/// # Example +/// +/// ```ignore +/// # use lang_evolve_core::decode_settings; +/// let str = r#"{"version":"1","categories":[],"rules":[]}"#; +/// let settings = decode_settings!(serde_json, str); +/// ``` +/// +/// [`Settings`]: ./settings/struct.Settings.html +#[macro_export(local_inner_macros)] +macro_rules! decode_settings { + ($funcrate:ident, $content:expr) => { + match $funcrate::from_str($content) { + Err(e) => { + log::error!("Could not import settings: {}", e.to_string()); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + e, + )); + } + Ok(val) => val, + } + }; +} + #[derive(Debug, Deserialize, Serialize)] pub struct Settings { #[serde(default = "Settings::get_ruleset_version")] @@ -75,37 +149,27 @@ impl Settings { /// let _s_yml = Settings::import(&path_yml).unwrap(); /// ``` pub fn import(path: &std::path::Path) -> std::io::Result { - use SettingsType::*; - let display = path.display(); - let file_type = utils::get_file_type(&path).unwrap(); - let content = match utils::read_file(&path) { - Err(e) => { - warn!("Could not read file {}: {}", display, e.to_string()); - return Err(e); - } - Ok(content) => content, - }; + use utils::SettingsType::{Json, Yaml}; + let file_type = utils::get_file_type(&path); + let content = utils::read_file(&path)?; let settings: Settings = match file_type { - Yaml => match serde_yaml::from_str(&content) { + Yaml => decode_settings!(serde_yaml, &content), + Json => decode_settings!(serde_json, &content), + // Attempt to decode anyway + _ => match Settings::from_str(&content.as_str()) { + Ok(val) => val, Err(e) => { - warn!("Could not import settings: {}", e.to_string()); + error!( + "Could not decode input {}: {}", + content, + e.to_string() + ); return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, + std::io::ErrorKind::InvalidData, e, )); } - Ok(val) => val, - }, - Json => match serde_json::from_str(&content) { - Err(e) => { - warn!("Could not import settings: {}", e.to_string()); - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - e, - )); - } - Ok(val) => val, }, }; info!("Successfuly imported {}", path.display()); @@ -140,28 +204,17 @@ impl Settings { /// s.export(&path_yml).unwrap(); /// ``` pub fn export(&self, path: &std::path::Path) -> std::io::Result<()> { - let filetype = utils::get_file_type(&path).unwrap(); + let filetype = utils::get_file_type(&path); let content = match filetype { - SettingsType::Yaml => match serde_yaml::to_string(&self) { - Err(e) => { - warn!("Could not serialize settings: {}", e.to_string()); - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - e, - )); - } - Ok(val) => val, - }, - SettingsType::Json => match serde_json::to_string(&self) { - Err(e) => { - warn!("Could not serialize settings: {}", e.to_string()); - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - e, - )); - } - Ok(val) => val, - }, + SettingsType::Yaml => encode_settings!(serde_yaml, &self), + SettingsType::Json => encode_settings!(serde_json, &self), + _ => { + error!("Unknown filetype {}", path.to_str().unwrap()); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Unknown file type", + )); + } }; info!("Successfuly exported settings to {}", path.display()); utils::write_file(&path, &content) @@ -173,6 +226,47 @@ impl Settings { } } +use std::str::FromStr; +impl FromStr for Settings { + type Err = serde_yaml::Error; + + /// Decode a litteral string into a `Settings` struct. Works only for + /// supported file types described in `SettingsType`. It will try to decode + /// the input `s` by any mean known by `SettingsType`. + /// + /// # Arguments + /// + /// * `s` - litteral string to decode into a `Settings` struct + /// + /// # Example + /// + /// ``` + /// # use std::str::FromStr; + /// let s = r#"{"version":"1","categories":[],"rules":[]}"#; + /// let settings = lang_evolve_core::settings::Settings::from_str(s).unwrap(); + /// ``` + fn from_str(s: &str) -> Result { + match serde_json::from_str::(s) { + Ok(val) => Ok(val), + Err(_) => match serde_yaml::from_str::(s) { + Ok(val) => Ok(val), + Err(e) => { + error!("Could not decode input {}: {}", s, e.to_string()); + return Err(e); + } + }, + } + } +} + +use std::fmt; +use std::fmt::Display; +impl Display for Settings { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", serde_json::to_string(&self).unwrap()) + } +} + impl PartialEq for Settings { fn eq(&self, other: &Self) -> bool { self.version == other.version @@ -181,28 +275,6 @@ impl PartialEq for Settings { } } -impl From for Settings -where - S: ToString, -{ - /// Import settings from file path described by the argument `source` - /// - /// # Arguments - /// - /// * `source` - path to the file from which settings should be imported - /// - /// # Example - /// - /// ```no_run - /// let s = lang_evolve_core::settings::Settings::from("settings.yml"); - /// ``` - fn from(source: S) -> Self { - let source = source.to_string(); - let path = std::path::Path::new(&source); - Settings::import(&path).unwrap() - } -} - impl Eq for Settings {} #[test]