1use config::builder::DefaultState;
2use config::{Config, ConfigBuilder, Environment};
3use lazy_static::{__Deref, lazy_static};
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6use std::sync::RwLock;
7
8use super::error::Result;
9use crate::types::LogLevel;
10
11lazy_static! {
14 pub static ref BUILDER: RwLock<ConfigBuilder<DefaultState>> = RwLock::new(Config::builder());
15}
16
17#[derive(Debug, Serialize, Deserialize)]
18pub struct Database {
19 pub url: String,
20 pub variable: String,
21}
22
23#[derive(Debug, Serialize, Deserialize)]
24pub struct AppConfig {
25 pub debug: bool,
26 pub log_level: LogLevel,
27 pub database: Database,
28}
29
30impl AppConfig {
31 pub fn init(default_config: Option<&str>) -> Result<()> {
33 let mut builder = Config::builder();
34
35 if let Some(config_contents) = default_config {
39 builder = builder.add_source(config::File::from_str(
41 config_contents,
42 config::FileFormat::Toml,
43 ));
44 }
45
46 builder = builder.add_source(Environment::with_prefix("APP"));
48
49 {
51 let mut w = BUILDER.write()?;
52 *w = builder;
53 }
54
55 Ok(())
56 }
57
58 pub fn merge_args(args: clap::ArgMatches) -> Result<()> {
59 if args.contains_id("debug") {
63 let value: &bool = args.get_one("debug").unwrap_or(&false);
64 AppConfig::set("debug", &value.to_string())?;
65 }
66
67 if args.contains_id("log_level") {
68 let value: &LogLevel = args.get_one("log_level").unwrap_or(&LogLevel::Info);
69 AppConfig::set("log_level", &value.to_string())?;
70 }
71
72 if args.contains_id("config") {
74 if let Some(config_path) = args.get_one::<String>("config") {
75 AppConfig::merge_config(Some(Path::new(config_path)))?;
76 }
77 }
78
79 Ok(())
80 }
81
82 pub fn init_with_args(
88 default_config: Option<&str>, args: Option<clap::ArgMatches>,
89 ) -> Result<()> {
90 AppConfig::init(default_config)?;
92
93 if let Some(arg_matches) = args {
95 AppConfig::merge_args(arg_matches)?;
96 }
97
98 Ok(())
99 }
100
101 pub fn merge_config(config_file: Option<&Path>) -> Result<()> {
102 if let Some(config_file_path) = config_file {
104 {
105 let mut w = BUILDER.write().unwrap();
106 *w = w.clone().add_source(config::File::with_name(
107 config_file_path.to_str().unwrap_or(""),
108 ));
109 }
110 }
111 Ok(())
112 }
113
114 pub fn set(key: &str, value: &str) -> Result<()> {
116 {
117 let mut w = BUILDER.write().unwrap();
118 *w = w.clone().set_override(key, value)?;
119 }
120
121 Ok(())
122 }
123
124 pub fn get<'de, T>(key: &'de str) -> Result<T>
126 where
127 T: serde::Deserialize<'de>,
128 {
129 Ok(BUILDER.read()?.deref().clone().build()?.get::<T>(key)?)
130 }
131
132 pub fn fetch() -> Result<AppConfig> {
136 let r = BUILDER.read()?;
138
139 let config_clone = r.deref().clone().build()?;
141
142 let app_config: AppConfig = config_clone.try_into()?;
144 Ok(app_config)
145 }
146}
147
148impl TryFrom<Config> for AppConfig {
151 type Error = crate::error::Error;
152
153 fn try_from(config: Config) -> Result<Self> {
154 Ok(AppConfig {
155 debug: config.get_bool("debug")?,
156 log_level: config.get::<LogLevel>("log_level")?,
157 database: config.get::<Database>("database")?,
158 })
159 }
160}