fregate/configuration/
application.rs1use crate::configuration::observability::ObservabilityConfig;
2use crate::configuration::source::ConfigSource;
3use crate::{error::Result, extensions::DeserializeExt, ManagementConfig};
4use config::{builder::DefaultState, ConfigBuilder, Environment, File, FileFormat};
5use serde::{
6 de::{DeserializeOwned, Error},
7 Deserialize, Deserializer,
8};
9use serde_json::Value;
10use std::marker::PhantomData;
11use std::{fmt::Debug, net::IpAddr};
12use tracing_appender::non_blocking::WorkerGuard;
13
14#[cfg(feature = "tls")]
15use crate::configuration::tls::TlsConfigurationVariables;
16
17const HOST_PTR: &str = "/host";
18const PORT_PTR: &str = "/port";
19const PORT_SERVER_PTR: &str = "/server/port";
20const MANAGEMENT_PTR: &str = "/management";
21
22const DEFAULT_CONFIG: &str = include_str!("../resources/default_conf.toml");
23const DEFAULT_SEPARATOR: &str = "_";
24
25#[derive(Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
27pub struct Empty {}
28
29#[derive(Debug)]
31pub struct AppConfig<ConfigExt = Empty> {
32 pub host: IpAddr,
34 pub port: u16,
37 pub observability_cfg: ObservabilityConfig,
39 pub management_cfg: ManagementConfig,
41 #[cfg(feature = "tls")]
43 pub tls: TlsConfigurationVariables,
44 pub private: ConfigExt,
46 pub worker_guard: Option<WorkerGuard>,
49}
50
51impl<ConfigExt> Clone for AppConfig<ConfigExt>
52where
53 ConfigExt: Clone,
54{
55 fn clone(&self) -> Self {
56 Self {
57 host: self.host,
58 port: self.port,
59 observability_cfg: self.observability_cfg.clone(),
60 management_cfg: self.management_cfg.clone(),
61 #[cfg(feature = "tls")]
62 tls: self.tls.clone(),
63 private: self.private.clone(),
64 worker_guard: None,
65 }
66 }
67}
68
69impl<'de, ConfigExt> Deserialize<'de> for AppConfig<ConfigExt>
70where
71 ConfigExt: Debug + DeserializeOwned,
72{
73 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
74 where
75 D: Deserializer<'de>,
76 {
77 let config = Value::deserialize(deserializer)?;
78 let host = config.pointer_and_deserialize(HOST_PTR)?;
79 let port = config
80 .pointer_and_deserialize(PORT_SERVER_PTR)
81 .or_else(|_err: D::Error| config.pointer_and_deserialize(PORT_PTR))?;
82
83 let management_cfg = config
84 .pointer_and_deserialize::<_, D::Error>(MANAGEMENT_PTR)
85 .unwrap_or_default();
86 let observability_cfg = ObservabilityConfig::deserialize(&config).map_err(Error::custom)?;
87 #[cfg(feature = "tls")]
88 let tls = TlsConfigurationVariables::deserialize(&config).map_err(Error::custom)?;
89 let private = ConfigExt::deserialize(config).map_err(Error::custom)?;
90
91 Ok(AppConfig::<ConfigExt> {
92 host,
93 port,
94 observability_cfg,
95 management_cfg,
96 #[cfg(feature = "tls")]
97 tls,
98 private,
99 worker_guard: None,
100 })
101 }
102}
103
104impl Default for AppConfig {
105 #[allow(clippy::expect_used)]
106 fn default() -> Self {
107 AppConfig::builder()
108 .add_default()
109 .add_env_prefixed("OTEL")
110 .build()
111 .expect("Default config never fails")
112 }
113}
114
115impl<ConfigExt> AppConfig<ConfigExt> {
116 pub fn builder() -> AppConfigBuilder<ConfigExt> {
118 AppConfigBuilder::new()
119 }
120
121 pub fn default_with(file_path: &str, env_prefix: &str) -> Result<Self>
125 where
126 ConfigExt: Debug + DeserializeOwned,
127 {
128 AppConfig::builder()
129 .add_default()
130 .add_env_prefixed("OTEL")
131 .add_file(file_path)
132 .add_env_prefixed(env_prefix)
133 .build()
134 }
135
136 pub fn load_from<'a, S>(sources: S) -> Result<Self>
138 where
139 ConfigExt: Debug + DeserializeOwned,
140 S: IntoIterator<Item = ConfigSource<'a>>,
141 {
142 let mut config_builder = AppConfig::<ConfigExt>::builder()
143 .add_default()
144 .add_env_prefixed("OTEL");
145
146 for source in sources {
147 config_builder = match source {
148 ConfigSource::String(str, format) => config_builder.add_str(str, format),
149 ConfigSource::File(path) => config_builder.add_file(path),
150 ConfigSource::EnvPrefix(prefix) => config_builder.add_env_prefixed(prefix),
151 };
152 }
153
154 config_builder.build()
155 }
156}
157
158#[derive(Debug, Default)]
160pub struct AppConfigBuilder<ConfigExt> {
161 builder: ConfigBuilder<DefaultState>,
162 phantom: PhantomData<ConfigExt>,
163}
164
165impl<ConfigExt> AppConfigBuilder<ConfigExt> {
166 pub fn new() -> Self {
168 Self {
169 builder: ConfigBuilder::default(),
170 phantom: PhantomData,
171 }
172 }
173
174 pub fn build(self) -> Result<AppConfig<ConfigExt>>
176 where
177 ConfigExt: Debug + DeserializeOwned,
178 {
179 Ok(self
180 .builder
181 .build()?
182 .try_deserialize::<AppConfig<ConfigExt>>()?)
183 }
184
185 #[must_use]
187 pub fn add_default(mut self) -> Self {
188 self.builder = self
189 .builder
190 .add_source(File::from_str(DEFAULT_CONFIG, FileFormat::Toml));
191 self
192 }
193
194 #[must_use]
196 pub fn add_file(mut self, path: &str) -> Self {
197 self.builder = self.builder.add_source(File::with_name(path));
198 self
199 }
200
201 #[must_use]
203 pub fn add_str(mut self, str: &str, format: FileFormat) -> Self {
204 self.builder = self.builder.add_source(File::from_str(str, format));
205 self
206 }
207
208 #[must_use]
210 pub fn add_env_prefixed(mut self, prefix: &str) -> Self {
211 self.builder = self.builder.add_source(
212 Environment::with_prefix(prefix)
213 .try_parsing(true)
214 .separator(DEFAULT_SEPARATOR),
215 );
216 self
217 }
218}