use ini::{Ini, Properties}; use std::{ path::{Path, PathBuf}, str::FromStr, }; use url::Url; pub struct InitConfig { pub db_url: String, pub btc_data_dir: Option, } impl InitConfig { /// Load from a file pub fn load_from_file(config_file: impl AsRef) -> Self { let conf = ini::Ini::load_from_file(config_file).unwrap(); assert(section(&conf, "taler"), "CURRENCY", "BTC"); let self_conf = section(&conf, "depolymerizer-bitcoin"); Self { db_url: require(self_conf, "DB_URL", string), btc_data_dir: path(self_conf, "BTC_DATA_DIR"), } } } /// Taler config with depolymerizer config #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { pub base_url: Url, pub db_url: String, pub port: u16, pub unix_path: Option, pub btc_data_dir: Option, pub payto: Url, pub confirmation: u8, pub bounce_fee: u64, pub btc_lifetime: Option, pub http_lifetime: Option, } impl Config { /// Load from a file pub fn load_from_file(config_file: impl AsRef) -> Self { let conf = ini::Ini::load_from_file(config_file).unwrap(); assert(section(&conf, "taler"), "CURRENCY", "BTC"); let ex_conf = section(&conf, "exchange"); let self_conf = section(&conf, "depolymerizer-bitcoin"); Self { base_url: require(ex_conf, "BASE_URL", url), db_url: require(self_conf, "DB_URL", string), port: nb(self_conf, "PORT").unwrap_or(8080), unix_path: path(self_conf, "UNIXPATH"), btc_data_dir: path(self_conf, "BTC_DATA_DIR"), payto: require(self_conf, "PAYTO", url), confirmation: nb(self_conf, "CONFIRMATION").unwrap_or(6), bounce_fee: nb(self_conf, "BOUNCE_FEE").unwrap_or(1000), btc_lifetime: nb(self_conf, "BTC_LIFETIME") .and_then(|nb| (nb != 0).then(|| Some(nb))) .unwrap_or(None), http_lifetime: nb(self_conf, "HTTP_LIFETIME") .and_then(|nb| (nb != 0).then(|| Some(nb))) .unwrap_or(None), } } } /* ----- Helper functions ----- */ fn section<'a>(ini: &'a Ini, name: &str) -> &'a Properties { ini.section(Some(name)) .unwrap_or_else(|| panic!("missing config section {}", name)) } fn assert(properties: &Properties, name: &str, expected: &str) { let value = require(properties, name, string); if value != expected { panic!("config {} expected '{}' got '{}'", name, expected, value); } } fn require( properties: &Properties, name: &str, lambda: fn(properties: &Properties, name: &str) -> Option, ) -> T { let result = lambda(properties, name); result.unwrap_or_else(|| panic!("missing config {}", name)) } fn string(properties: &Properties, name: &str) -> Option { properties.get(name).map(|s| s.to_string()) } fn path(properties: &Properties, name: &str) -> Option { properties.get(name).map(|s| { PathBuf::from_str(s).unwrap_or_else(|_| panic!("config value {} is not a valid path", name)) }) } fn nb(properties: &Properties, name: &str) -> Option { properties.get(name).map(|s| { s.parse() .unwrap_or_else(|_| panic!("config value {} is not a number", name)) }) } fn url(properties: &Properties, name: &str) -> Option { properties.get(name).map(|s| { Url::parse(s).unwrap_or_else(|_| panic!("config value {} is not a valid url", name)) }) }