arachnid_cli/config/
settings.rs1use super::{kinds::*, types::*};
6
7type ConfigBuilder<S = config::builder::DefaultState> = config::builder::ConfigBuilder<S>;
8
9type ConfigResult<T> = core::result::Result<T, config::ConfigError>;
10
11fn _load_env_or_default(env: &str, default: impl ToString) -> String {
12 std::env::var(env).unwrap_or_else(|_| default.to_string())
13}
14
15fn source_file(
16 dir: impl ToString,
17 name: impl ToString,
18) -> config::File<config::FileSourceFile, config::FileFormat> {
19 let fname = format!("{p}/{f}", p = dir.to_string(), f = name.to_string());
20 config::File::with_name(&fname)
21}
22
23fn with_sources<T: core::fmt::Display>(
24 ctx: ConfigBuilder,
25 workdir: &str,
26 names: impl IntoIterator<Item = T>,
27) -> ConfigBuilder {
28 let mut tmp = ctx;
29 for n in names {
30 tmp = tmp.add_source(source_file(workdir, n).required(false));
31 }
32 tmp
33}
34
35fn set_default(builder: ConfigBuilder) -> ConfigResult<ConfigBuilder> {
36 let builder = builder
37 .set_default("mode", "debug")?
38 .set_default("name", crate::config::APP_NAME)?
39 .set_default("version", env!("CARGO_PKG_VERSION"))?
40 .set_default("scope.context", ".")?
41 .set_default("scope.workdir", crate::config::DEFAULT_WORKDIR)?
42 .set_default("network.address.host", crate::config::DEFAULT_HOST)?
43 .set_default("network.address.port", crate::config::DEFAULT_PORT)?
44 .set_default("services.tracing.level", "info")?;
45 Ok(builder)
46}
47
48fn add_sources(builder: ConfigBuilder) -> ConfigBuilder {
49 let workdir = _load_env_or_default("APP_CONFIG_DIR", crate::config::DEFAULT_DIR_CONFIG);
50 let fname = _load_env_or_default("APP_CONFIG_FILE", crate::config::DEFAULT_CONFIG_FILE);
52 let builder = with_sources(
54 builder,
55 &workdir,
56 &[
57 "default.config",
58 "debug.config",
59 "development.config",
60 "app.config",
61 "prod.config",
62 &fname,
63 ],
64 );
65
66 builder
67 .add_source(config::Environment::with_prefix("APP").separator("_"))
68 .add_source(config::File::with_name(&fname).required(false))
69}
70
71fn set_overrides(builder: ConfigBuilder) -> ConfigResult<ConfigBuilder> {
72 Ok({
73 builder
74 .set_override_option("mode", std::env::var("APP_MODE").ok())?
75 .set_override_option("name", std::env::var("APP_NAME").ok())?
76 .set_override_option("network.address.host", std::env::var("APP_HOST").ok())?
77 .set_override_option("network.address.port", std::env::var("APP_PORT").ok())?
78 .set_override_option("workspace.workdir", std::env::var("APP_WORKDIR").ok())?
79 })
80}
81
82#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
83#[serde(default, deny_unknown_fields, rename_all = "snake_case")]
84pub struct Settings {
85 pub mode: Mode,
86 pub name: String,
87 pub network: NetworkConfig,
88 pub scope: Scope,
89 pub services: ServicesConfig,
90 pub version: String,
91 pub workspace: WorkspaceConfig,
92}
93
94impl Settings {
95 pub const DEFAULT_MODE: Mode = Mode::Debug;
96 pub const APP_NAME: &'static str = "arachnid";
97 pub fn new() -> Self {
100 Self::build().unwrap_or_default()
101 }
102 pub fn from_mode(mode: Mode) -> Self {
104 Self {
105 mode,
106 name: Self::APP_NAME.to_string(),
107 network: NetworkConfig::default(),
108 scope: Scope::default(),
109 services: ServicesConfig::default(),
110 version: env!("CARGO_PKG_VERSION").to_string(),
111 workspace: WorkspaceConfig::default(),
112 }
113 }
114 pub fn debug(self) -> Self {
116 Self {
117 mode: Mode::Debug,
118 ..self
119 }
120 }
121 pub fn release(self) -> Self {
123 Self {
124 mode: Mode::Release,
125 ..self
126 }
127 }
128 pub fn build() -> Result<Self, config::ConfigError> {
130 Self::builder_base()?.build()?.try_deserialize()
131 }
132 pub const fn mode(&self) -> Mode {
134 self.mode
135 }
136 pub fn name(&self) -> &str {
138 &self.name
139 }
140 pub const fn network(&self) -> &NetworkConfig {
142 &self.network
143 }
144 pub const fn network_mut(&mut self) -> &mut NetworkConfig {
146 &mut self.network
147 }
148 pub const fn scope(&self) -> &Scope {
150 &self.scope
151 }
152 pub const fn scope_mut(&mut self) -> &mut Scope {
154 &mut self.scope
155 }
156 pub const fn services(&self) -> &ServicesConfig {
158 &self.services
159 }
160 pub const fn services_mut(&mut self) -> &mut ServicesConfig {
162 &mut self.services
163 }
164 pub fn version(&self) -> &str {
166 &self.version
167 }
168 pub const fn workspace(&self) -> &WorkspaceConfig {
170 &self.workspace
171 }
172 pub const fn workspace_mut(&mut self) -> &mut WorkspaceConfig {
174 &mut self.workspace
175 }
176 pub async fn bind(&self) -> std::io::Result<tokio::net::TcpListener> {
179 self.network().bind().await
180 }
181 pub fn init_tracing(&self) {
183 self.services().tracing().init_tracing(self.name());
184 }
185 pub fn set_workdir<T>(&mut self, workdir: T)
187 where
188 std::path::PathBuf: From<T>,
189 {
190 self.workspace_mut().set_workdir(workdir.into());
191 }
192 pub fn set_workdir_option<T>(&mut self, workdir: Option<T>)
194 where
195 std::path::PathBuf: From<T>,
196 {
197 workdir.map(|w| self.set_workdir(w));
198 }
199
200 pub fn set_port(&mut self, port: u16) {
201 self.network_mut().set_port(port);
202 }
203
204 pub fn set_log_level(&mut self, level: LogLevel) {
205 self.services_mut().tracing_mut().set_level(level);
206 }
207
208 fn builder_base() -> ConfigResult<ConfigBuilder> {
209 let mut builder = config::Config::builder();
211 builder = set_default(builder)?;
213 builder = add_sources(builder);
215 builder = set_overrides(builder)?;
217 Ok(builder)
219 }
220}
221
222impl Default for Settings {
223 fn default() -> Self {
224 Self::from_mode(Mode::Debug)
225 }
226}
227
228impl core::fmt::Debug for Settings {
229 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
230 f.write_str(&serde_json::to_string_pretty(&self).unwrap())
231 }
232}
233
234impl core::fmt::Display for Settings {
235 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
236 f.write_str(&serde_json::to_string(self).unwrap())
237 }
238}