quick_error! {
#[derive(Debug)]
pub enum SettingsError {
ConfigError(err: config::ConfigError) {
display("{}", err)
from()
}
IOError(err: std::io::Error) {
display("{}", err)
from()
}
}
}
#[derive(Debug)]
pub struct Settings;
use std::sync::Arc;
impl Settings {
pub fn load(submod: &str) -> Result<Arc<SettingsDerive>, SettingsError> {
validate_submod("upsert", submod);
let settings_config = SettingsDerive::merge_all(submod)?;
SettingsContainer::upsert(submod, settings_config)?;
SettingsContainer::handle(submod)
}
}
use config::{Config, Environment, File};
use serde::Deserialize;
#[allow(unused_imports)]
use validator::{Validate, ValidationError};
#[derive(Debug, Default, Validate, Deserialize, Clone, PartialEq)]
pub struct MatcherSettingsDerive {
#[validate(non_control_character)]
catalog: String,
#[validate(url)]
pub rest_url: Option<String>,
#[validate(non_control_character)]
pub query: Option<String>,
#[validate(non_control_character)]
pub matcher: Option<String>,
}
use std::collections::HashMap;
#[derive(Debug, Default, Deserialize, Clone, PartialEq)]
pub struct SettingsDerive {
pub fetcher_matcher: MatcherSettingsDerive,
}
use std::env;
use std::path::PathBuf;
impl SettingsDerive {
fn merge_all(submod: &str) -> Result<Self, SettingsError> {
validate_submod("upsert", submod);
let mut s = Config::default();
let mut path_locator = match env::var_os("GIS_CONFIG_PATH") {
Some(val) => PathBuf::from(val),
None => env::current_dir().unwrap().join("config"),
};
path_locator.push(submod);
s.merge(File::from(path_locator.join("default")).required(false))?;
let env_mode = env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
s.merge(File::from(path_locator.join(env_mode)).required(true))?;
s.merge(Environment::with_prefix(submod))?;
match s.try_into() {
Ok(val) => Ok(val),
Err(e) => Err(SettingsError::ConfigError(e)),
}
}
}
#[derive(Debug)]
pub struct SettingsContainer {
settings: HashMap<String, SettingsDerive>,
}
use state::Storage;
use std::sync::RwLock;
static SETTINGS_CONTAINER: Storage<RwLock<SettingsContainer>> = Storage::new();
fn validate_submod(func: &str, submod: &str) {
assert!(
(submod.len() == 2) && submod.chars().all(char::is_alphanumeric),
"Caller bug: {}() submod must be ISO 3166-1_alpha-2 and {} is not.",
func,
submod
);
}
impl SettingsContainer {
fn handle(submod: &str) -> Result<Arc<SettingsDerive>, SettingsError> {
validate_submod("upsert", submod);
let mut_container = SETTINGS_CONTAINER.get().read().unwrap();
let sub_config = mut_container.settings.get(submod).unwrap();
Ok(Arc::new(sub_config.clone()))
}
fn upsert(submod: &str, settings: SettingsDerive) -> Result<bool, SettingsError> {
validate_submod("upsert", submod);
let mut_container = SETTINGS_CONTAINER.try_get();
match mut_container {
Some(_) => {
let config_all_lock = mut_container.unwrap();
let mut config_all = config_all_lock.write().unwrap();
config_all.settings.insert(submod.to_string(), settings);
}
None => {
let mut init_map = SettingsContainer {
settings: HashMap::new(),
};
init_map.settings.insert(submod.to_string(), settings);
SETTINGS_CONTAINER.set(RwLock::new(init_map));
}
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
use proptest_derive::Arbitrary;
use rstest::rstest;
use rstest_reuse::{self, *};
#[derive(Arbitrary, Default, Debug)]
struct MatcherSettingsDeriveTest {
catalog: String,
rest_url: Option<String>,
query: Option<String>,
matcher: Option<String>,
}
fn settings_derive(fetcher_matcher_val: MatcherSettingsDerive) -> SettingsDerive {
SettingsDerive {
fetcher_matcher: fetcher_matcher_val,
}
}
fn fetcher_matcher_test(derive_test: MatcherSettingsDeriveTest) -> MatcherSettingsDerive {
MatcherSettingsDerive {
catalog: derive_test.catalog,
rest_url: derive_test.rest_url,
query: derive_test.query,
matcher: derive_test.matcher,
}
}
#[template]
#[rstest(submod,
case("au"),
// no way knowing xx !exist so treat as valid for now.
// could always hardcode the submod list but benefit?
case("xx")
)
]
fn upsert_submod_valid_isostr(submod: &str) {
let res = SettingsContainer::upsert(submod, SettingsDerive::default());
assert!(res.unwrap());
}
#[apply(upsert_submod_valid_isostr)]
fn handle_submod_valid_isostr(submod: &str) {
let _foo = SettingsContainer::upsert(submod, SettingsDerive::default());
let _res = SettingsContainer::handle(submod).unwrap();
}
#[template]
#[rstest(submod, case(")("), case("aaa"), case("+++"))]
#[should_panic]
fn upsert_submod_invalid_isostr(submod: &str) {
let test_config =
settings_derive(fetcher_matcher_test(MatcherSettingsDeriveTest::default()));
let _res = SettingsContainer::upsert(submod, test_config);
}
#[apply(upsert_submod_invalid_isostr)]
fn handle_submod_invalid_isostr(submod: &str) {
let test_config =
settings_derive(fetcher_matcher_test(MatcherSettingsDeriveTest::default()));
let _res = SettingsContainer::upsert("au", test_config);
let _res = SettingsContainer::handle(submod);
}
proptest! {
#[test]
fn upsert_fetcher_matcher(derive_test: MatcherSettingsDeriveTest) {
let test_config = settings_derive(fetcher_matcher_test(derive_test));
let res = SettingsContainer::upsert("au", test_config);
assert!(res.unwrap());
}
}
}