fregate/configuration/
application.rs

1use 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/// Default private config for [`AppConfig`].
26#[derive(Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
27pub struct Empty {}
28
29/// AppConfig reads and saves application configuration from different sources
30#[derive(Debug)]
31pub struct AppConfig<ConfigExt = Empty> {
32    /// host address where to start Application
33    pub host: IpAddr,
34    /// When serialized uses `<PREFIX>`_PORT or `<PREFIX>`_SERVER_PORT names.
35    /// `<PREFIX>`_SERVER_PORT has higher priority.
36    pub port: u16,
37    /// configuration for logs and traces
38    pub observability_cfg: ObservabilityConfig,
39    /// configures management endpoints
40    pub management_cfg: ManagementConfig,
41    /// TLS configuration parameters
42    #[cfg(feature = "tls")]
43    pub tls: TlsConfigurationVariables,
44    /// field for each application specific configuration
45    pub private: ConfigExt,
46    /// Why it is here read more: [`https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.WorkerGuard.html`]
47    /// This one will not be cloned and will be set to [`None`] in clone.
48    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    /// Creates [`AppConfigBuilder`] to add different sources to config
117    pub fn builder() -> AppConfigBuilder<ConfigExt> {
118        AppConfigBuilder::new()
119    }
120
121    /// Load file by given path and add environment variables with given prefix in addition to default config
122    ///
123    /// Environment variables have highet priority then file and then default configuration
124    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    /// Load configuration from provided container with [`ConfigSource`] which override default config.
137    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/// AppConfig builder to set up multiple sources
159#[derive(Debug, Default)]
160pub struct AppConfigBuilder<ConfigExt> {
161    builder: ConfigBuilder<DefaultState>,
162    phantom: PhantomData<ConfigExt>,
163}
164
165impl<ConfigExt> AppConfigBuilder<ConfigExt> {
166    /// Creates new [`AppConfigBuilder`]
167    pub fn new() -> Self {
168        Self {
169            builder: ConfigBuilder::default(),
170            phantom: PhantomData,
171        }
172    }
173
174    /// Reads all registered sources
175    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    /// Add default config
186    #[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    /// Add file
195    #[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    /// Add string
202    #[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    /// Add environment variables with specified prefix and default separator: "_"
209    #[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}