|
| 1 | +use config::{Config, File}; |
| 2 | +use serde_derive::Deserialize; |
| 3 | +use std::sync::OnceLock; |
| 4 | +use std::sync::RwLock; |
| 5 | + |
| 6 | + |
| 7 | +#[derive(Default, Clone, Deserialize)] |
| 8 | +struct Cred { |
| 9 | + user: String, |
| 10 | + key: String, |
| 11 | +} |
| 12 | + |
| 13 | +#[derive(Default, Clone, Deserialize)] |
| 14 | +pub struct Settings { |
| 15 | + verbose: Option<u8>, |
| 16 | + cred: Option<Cred>, |
| 17 | +} |
| 18 | + |
| 19 | + |
| 20 | +// This function defines the static settings storage. |
| 21 | +fn settings() -> &'static RwLock<Settings> { |
| 22 | + static SETTINGS: OnceLock<RwLock<Settings>> = OnceLock::new(); |
| 23 | + SETTINGS.get_or_init(|| RwLock::new(Settings::default())) |
| 24 | +} |
| 25 | + |
| 26 | +fn build_config(file: &str) -> Settings { |
| 27 | + let s = Config::builder() |
| 28 | + // Configuration file |
| 29 | + .add_source(File::with_name(file).required(false)) |
| 30 | + .build() |
| 31 | + .expect("Config build failed"); |
| 32 | + |
| 33 | + // Deserialize (and thus freeze) the entire configuration |
| 34 | + s.try_deserialize().unwrap() |
| 35 | +} |
| 36 | + |
| 37 | +impl Settings { |
| 38 | + // This associated function replaces previous settings values with a newly |
| 39 | + // loaded ones. |
| 40 | + // |
| 41 | + // It is mainly intended for loading the values from config file to replace |
| 42 | + // the plain default used for static allocation. Thus running it once at |
| 43 | + // the beginning of the program execution when the config files are known. |
| 44 | + // |
| 45 | + // But a subsequent call to this function may be used to update the settings, |
| 46 | + // for example when the config file change during the execution and you want |
| 47 | + // to sync with it (signal/notify/whatever based reload). |
| 48 | + pub fn init(cfgfile: Option<&str>) { |
| 49 | + let file = match cfgfile { |
| 50 | + Some(x) => x, |
| 51 | + None => "config.toml" |
| 52 | + }; |
| 53 | + |
| 54 | + let mut new_settings = settings().write().unwrap(); |
| 55 | + *new_settings = build_config(file); |
| 56 | + } |
| 57 | + |
| 58 | + // Following associated functions are just getters, when you want to keep |
| 59 | + // the Settings structure members private. |
| 60 | + pub fn user() -> Result<String, String> { |
| 61 | + match &settings().read().unwrap().cred { |
| 62 | + Some(c) => Ok(c.user.clone()), |
| 63 | + None => Err(format!("Credential config is missing")) |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + pub fn key() -> Result<String, String> { |
| 68 | + match &settings().read().unwrap().cred { |
| 69 | + Some(c) => Ok(c.key.clone()), |
| 70 | + None => Err(format!("Credential config is missing")) |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + pub fn verbosity() -> u8 { |
| 75 | + match settings().read().unwrap().verbose { |
| 76 | + Some(v) => v, |
| 77 | + None => 0 |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + // It is not a problem to make all the Settings structure members public and |
| 82 | + // then only create here one function, that will return a read reference |
| 83 | + // to the Settings. This may be useful if you want to omit the getters and |
| 84 | + // the settings contains just plain values, that doesn't need any error |
| 85 | + // handling. |
| 86 | + // |
| 87 | + // Example: |
| 88 | + // pub fn new() -> Self { |
| 89 | + // settings().read().unwrap() |
| 90 | + // } |
| 91 | +} |
| 92 | + |
0 commit comments