From 6423baebd92e45c9be85157c443dff42e65d8d14 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 1 Sep 2023 00:00:00 +0000 Subject: refactor: rebuild app skeleton Create skeleton for a complete rebuild of the prototype as a production ready product. Includes design patterns for: - dependency injection - unit testing with dependency mocking - integration testing - error handling - config storage BREAKING-CHANGE: ground-up redesign with incompatible protocol standards --- src/config.rs | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/config.rs (limited to 'src/config.rs') diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..b26dea0 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,152 @@ +use std::{fs::File, io::BufReader}; + +use anyhow::{anyhow, Context, Result}; +use directories::ProjectDirs; +#[cfg(test)] +use mockall::*; +use serde::{self, Deserialize, Serialize}; + +#[derive(Default)] +#[allow(clippy::module_name_repetitions)] +pub struct ConfigManager; + +#[cfg_attr(test, automock)] +#[allow(clippy::module_name_repetitions)] +pub trait ConfigManagement { + fn load(&self) -> Result; + fn save(&self, cfg: &MyConfig) -> Result<()>; +} + +pub fn get_dirs() -> Result { + ProjectDirs::from("", "CodeCollaboration", "ngit").ok_or(anyhow!( + "should find operating system home directories with rust-directories crate" + )) +} + +impl ConfigManagement for ConfigManager { + fn load(&self) -> Result { + let config_path = get_dirs()?.config_dir().join("config.json"); + if config_path.exists() { + let file = + File::open(config_path).context("should open application configuration file")?; + let reader = BufReader::new(file); + let config: MyConfig = serde_json::from_reader(reader) + .context("should read config from config file with serde_json")?; + Ok(config) + } else { + Ok(MyConfig::default()) + } + } + fn save(&self, cfg: &MyConfig) -> Result<()> { + let dirs = get_dirs()?; + let config_path = dirs.config_dir().join("config.json"); + let file = if config_path.exists() { + std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(config_path) + .context( + "should open application configuration file with write and truncate options", + )? + } else { + std::fs::create_dir_all(dirs.config_dir()) + .context("should create application config directories")?; + std::fs::File::create(config_path).context("should create application config file")? + }; + serde_json::to_writer_pretty(file, cfg) + .context("should write configuration to config file with serde_json") + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)] +#[allow(clippy::module_name_repetitions)] +pub struct MyConfig { + pub version: u8, + pub users: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct UserRef { + pub nsec: String, +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use serial_test::serial; + use test_utils::*; + + use super::*; + + mod load { + use super::*; + + #[test] + #[serial] + fn when_config_file_doesnt_exist_defaults_are_returned() -> Result<()> { + with_fresh_config(|| { + assert_eq!(ConfigManager.load()?, MyConfig::default()); + + Ok(()) + }) + } + + #[test] + #[serial] + fn when_config_file_exists_it_is_returned() -> Result<()> { + with_fresh_config(|| { + let c = ConfigManager; + let new_config = MyConfig { + version: 255, + ..MyConfig::default() + }; + c.save(&new_config)?; + assert_eq!(c.load()?, new_config); + + Ok(()) + }) + } + } + + mod save { + use super::*; + + #[test] + #[serial] + fn when_config_file_doesnt_config_is_saved() -> Result<()> { + with_fresh_config(|| { + let c = ConfigManager; + let new_config = MyConfig { + version: 255, + ..MyConfig::default() + }; + c.save(&new_config)?; + assert_eq!(c.load()?, new_config); + + Ok(()) + }) + } + + #[test] + #[serial] + fn when_config_file_exists_new_config_is_saved() -> Result<()> { + with_fresh_config(|| { + let c = ConfigManager; + let config = MyConfig { + version: 255, + ..MyConfig::default() + }; + c.save(&config)?; + let new_config = MyConfig { + version: 254, + ..MyConfig::default() + }; + c.save(&new_config)?; + assert_eq!(c.load()?, new_config); + + Ok(()) + }) + } + } +} -- cgit v1.2.3