be-technical-interview-rust/src/defs/config.rs

139 lines
4.1 KiB
Rust

//! # Config settings definitions
//! To load config values from TOML file path, it can be provided via command-line arguments <br>
//! It use [serde](https://serde.rs/) via [`load_from_file`]
//
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; // ,Deserializer,Serializer};
use std::io::{Error, ErrorKind, Result};
use crate::{defs::CliSettings, CFG_FILE_EXTENSION, DEFAULT_INPUT_PATH, DEFAULT_REG_EXP};
fn default_config_input() -> String {
String::from("input.txt")
}
fn default_config_output() -> String {
String::from("")
}
fn default_config_overwrite() -> bool {
true
}
fn default_config_be_quiet() -> bool {
false
}
fn default_config_reg_exp() -> String {
String::from(DEFAULT_REG_EXP)
}
fn default_config_targets() -> Vec<ConfigTarget> {
vec![ConfigTarget::default()]
}
/// Settings for each target metric defined in **config path**
/// **config.toml** content example:
/// ```toml
/// be_quiet = false
/// [[targets]]
/// input = "input.txt"
///
/// [[targets]]
/// input = "input_2.txt"
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigTarget {
#[serde(default = "default_config_input")]
pub input: String,
#[serde(default = "default_config_output")]
pub output: String,
#[serde(default = "default_config_overwrite")]
pub out_overwrite: bool,
#[serde(default = "default_config_reg_exp")]
pub reg_exp: String,
}
impl Default for ConfigTarget {
fn default() -> Self {
Self {
input: String::from(DEFAULT_INPUT_PATH),
output: String::from(""),
out_overwrite: true,
reg_exp: String::from(DEFAULT_REG_EXP),
}
}
}
/// Config Settings with target metric settings [`ConfigTarget`]
/// **config.toml** content example:
/// ```toml
/// be_quiet = false
/// [[targets]]
/// input = "input.txt"
///
/// [[targets]]
/// input = "input_2.txt"
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default = "default_config_be_quiet")]
pub be_quiet: bool,
#[serde(default = "default_config_targets")]
pub targets: Vec<ConfigTarget>,
}
impl Default for Config {
fn default() -> Self {
Self {
be_quiet: false,
targets: vec![ConfigTarget::default()],
}
}
}
impl Config {
// To override [`Config`] values with [`CliSettings`] provided via command line arguments and loaded via [`parse_args`]
pub fn add_cli_settings(&self, cli_settings: CliSettings) -> Self {
let be_quiet = if let Some(be_quiet) = cli_settings.be_quiet {
be_quiet
} else {
self.be_quiet
};
let targets = if let Some(input) = cli_settings.input {
let mut target = ConfigTarget {
input,
..Default::default()
};
if let Some(output) = cli_settings.output {
target.output = output
}
vec![target]
} else {
self.targets.to_owned()
};
Self { be_quiet, targets }
}
}
/// To load config settings and **Deserialize** content to [`Config`] struct <br>
/// It use **T** as generic, allowing to load from a file to a **T** type
/// It use **fs::read_to_string** as it is expecting short files size, no need to buffers.
pub fn load_from_file<T: DeserializeOwned>(file_cfg: &str) -> Result<T> {
let file_path = if file_cfg.contains(CFG_FILE_EXTENSION) {
file_cfg.to_string()
} else {
format!("{}{}", file_cfg, CFG_FILE_EXTENSION)
};
let config_content = match std::fs::read_to_string(&file_path) {
Ok(cfgcontent) => cfgcontent,
Err(e) => {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Error read {}: {}", &file_path, e),
))
}
};
let item_cfg = match toml::from_str::<T>(&config_content) {
Ok(cfg) => cfg,
Err(e) => {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Error loading config {}: {}", &file_path, e),
))
}
};
Ok(item_cfg)
}