From ba2100cb29c6b8254c291238fb71af70bcab35ca Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 20 Jun 2021 11:10:46 +0200 Subject: [PATCH 1/3] Refactor: shorten fn load impl Signed-off-by: Matthias Beyer --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0717c77..b3bbd4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,9 +185,7 @@ pub fn load<'a, T: Serialize + DeserializeOwned + Default>( app_name: &str, config_name: impl Into>, ) -> Result { - let path = get_configuration_file_path(app_name, config_name)?; - - load_path(path) + get_configuration_file_path(app_name, config_name).and_then(load_path) } /// Load an application configuration from a specified path. From b7f5a764040a8e3af935f7842c8ec04ed92e015e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 20 Jun 2021 12:13:27 +0200 Subject: [PATCH 2/3] Implement format specification with generic arguments This patch implements config format specification by providing generic arguments to the API functions. The type provided in the generic argument implements the deserialization technique. The example was adapted. Signed-off-by: Matthias Beyer --- examples/simple.rs | 6 +-- src/lib.rs | 111 +++++++++++++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 1e8f9cf..d4504de 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -25,8 +25,8 @@ impl Default for ConfyConfig { } fn main() -> Result<(), confy::ConfyError> { - let cfg: ConfyConfig = confy::load("confy_simple_app", None)?; - let file = confy::get_configuration_file_path("confy_simple_app", None)?; + let cfg: ConfyConfig = confy::load::("confy_simple_app", None)?; + let file = confy::get_configuration_file_path::("confy_simple_app", None)?; println!("The configuration file path is: {:#?}", file); println!("The configuration is:"); println!("{:#?}", cfg); @@ -41,7 +41,7 @@ fn main() -> Result<(), confy::ConfyError> { name: "Test".to_string(), ..cfg }; - confy::store("confy_simple_app",None, &cfg)?; + confy::store::("confy_simple_app",None, &cfg)?; println!("The updated toml file content is:"); let mut content = String::new(); std::fs::File::open(&file) diff --git a/src/lib.rs b/src/lib.rs index b3bbd4b..44ed9fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,11 +87,60 @@ NOTE: `toml_conf` is a default feature, so disabling it might mean switching off default features for confy in your Cargo.toml" ); +pub trait Format { + fn extension() -> &'static str; + + fn deserialize(cfg_string: S) -> Result + where T: Serialize + DeserializeOwned + Default, + S: AsRef; +} + +#[cfg(feature = "toml_conf")] +mod format_toml { + use serde::{de::DeserializeOwned, Serialize}; + use crate::ConfyError; + use crate::Format; + + pub struct Toml; + impl Format for Toml { + fn extension() -> &'static str { + "toml" + } + + fn deserialize(cfg_string: S) -> Result + where T: Serialize + DeserializeOwned + Default, + S: AsRef + { + toml::from_str(cfg_string.as_ref()).map_err(ConfyError::BadTomlData) + } + } +} #[cfg(feature = "toml_conf")] -const EXTENSION: &str = "toml"; +pub use crate::format_toml::*; #[cfg(feature = "yaml_conf")] -const EXTENSION: &str = "yml"; +mod format_yaml { + use serde::{de::DeserializeOwned, Serialize}; + use crate::ConfyError; + use crate::Format; + + pub struct Yaml; + impl Format for Yaml { + fn extension() -> &'static str { + "yml" + } + + fn deserialize(cfg_string: S) -> Result + where T: Serialize + DeserializeOwned + Default, + S: AsRef + { + serde_yaml::from_str(cfg_string.as_ref()).map_err(ConfyError::BadYamlData) + } + } +} +#[cfg(feature = "yaml_conf")] +pub use crate::format_yaml::*; + /// The errors the confy crate can encounter. #[derive(Debug)] @@ -181,11 +230,12 @@ impl Error for ConfyError {} /// # Ok(()) /// # } /// ``` -pub fn load<'a, T: Serialize + DeserializeOwned + Default>( - app_name: &str, - config_name: impl Into>, -) -> Result { - get_configuration_file_path(app_name, config_name).and_then(load_path) +pub fn load<'a, F, T, C>(app_name: &str, config_name: C) -> Result + where F: Format, + T: Serialize + DeserializeOwned + Default, + C: Into> +{ + get_configuration_file_path::(app_name, config_name).and_then(|p| load_path::(p)) } /// Load an application configuration from a specified path. @@ -198,25 +248,16 @@ pub fn load<'a, T: Serialize + DeserializeOwned + Default>( /// and behavior, see [`load`]'s documentation. /// /// [`load`]: fn.load.html -pub fn load_path( - path: impl AsRef, -) -> Result { +pub fn load_path(path: P) -> Result + where F: Format, + T: Serialize + DeserializeOwned + Default, + P: AsRef +{ match File::open(&path) { Ok(mut cfg) => { - let cfg_string = cfg - .get_string() - .map_err(ConfyError::ReadConfigurationFileError)?; - - #[cfg(feature = "toml_conf")] - { - let cfg_data = toml::from_str(&cfg_string); - cfg_data.map_err(ConfyError::BadTomlData) - } - #[cfg(feature = "yaml_conf")] - { - let cfg_data = serde_yaml::from_str(&cfg_string); - cfg_data.map_err(ConfyError::BadYamlData) - } + cfg.get_string() + .map_err(ConfyError::ReadConfigurationFileError) + .and_then(F::deserialize) } Err(ref e) if e.kind() == NotFound => { if let Some(parent) = path.as_ref().parent() { @@ -258,12 +299,12 @@ pub fn load_path( /// able to write the configuration file or if `confy` /// encounters an operating system or environment it does /// not support. -pub fn store<'a, T: Serialize>( - app_name: &str, - config_name: impl Into>, - cfg: T, -) -> Result<(), ConfyError> { - let path = get_configuration_file_path(app_name, config_name)?; +pub fn store<'a, F, T, C>(app_name: &str, config_name: C, cfg: T) -> Result<(), ConfyError> + where F: Format, + T: Serialize, + C: Into>, +{ + let path = get_configuration_file_path::(app_name, config_name)?; fs::create_dir_all(&path.parent().unwrap()).map_err(ConfyError::DirectoryCreationFailed)?; store_path(path, cfg) @@ -305,16 +346,16 @@ pub fn store_path(path: impl AsRef, cfg: T) -> Result<(), Co /// /// [`load`]: fn.load.html /// [`store`]: fn.store.html -pub fn get_configuration_file_path<'a>( - app_name: &str, - config_name: impl Into>, -) -> Result { +pub fn get_configuration_file_path<'a, F, C>(app_name: &str, config_name: C) -> Result + where F: Format, + C: Into> +{ let config_name = config_name.into().unwrap_or("default-config"); let project = ProjectDirs::from("rs", "", app_name).ok_or(ConfyError::BadConfigDirectoryStr)?; let config_dir_str = get_configuration_directory_str(&project)?; - let path = [config_dir_str, &format!("{}.{}", config_name, EXTENSION)] + let path = [config_dir_str, &format!("{}.{}", config_name, F::extension())] .iter() .collect(); From 11d45443c49049fb95c1108f6b0ac82be772318a Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 20 Jun 2021 12:16:11 +0200 Subject: [PATCH 3/3] Remove restriction to have either toml or yaml Signed-off-by: Matthias Beyer --- src/lib.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 44ed9fe..75a0ec3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,14 +79,6 @@ confy. Please enable one of either the `toml_conf` or `yaml_conf` \ features." ); -#[cfg(all(feature = "toml_conf", feature = "yaml_conf"))] -compile_error!( - "Exactly one config language feature must be enabled to compile \ -confy. Please disable one of either the `toml_conf` or `yaml_conf` features. \ -NOTE: `toml_conf` is a default feature, so disabling it might mean switching off \ -default features for confy in your Cargo.toml" -); - pub trait Format { fn extension() -> &'static str;