1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
use crate::configuration::observability::ObservabilityConfig;
use crate::configuration::source::ConfigSource;
use crate::{error::Result, extensions::DeserializeExt, ManagementConfig};
use config::{builder::DefaultState, ConfigBuilder, Environment, File, FileFormat};
use serde::{
    de::{DeserializeOwned, Error},
    Deserialize, Deserializer,
};
use serde_json::Value;
use std::marker::PhantomData;
use std::{fmt::Debug, net::IpAddr};
use tracing_appender::non_blocking::WorkerGuard;

#[cfg(feature = "tls")]
use crate::configuration::tls::TlsConfigurationVariables;

const HOST_PTR: &str = "/host";
const PORT_PTR: &str = "/port";
const PORT_SERVER_PTR: &str = "/server/port";
const MANAGEMENT_PTR: &str = "/management";

const DEFAULT_CONFIG: &str = include_str!("../resources/default_conf.toml");
const DEFAULT_SEPARATOR: &str = "_";

/// Default private config for [`AppConfig`].
#[derive(Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
pub struct Empty {}

/// AppConfig reads and saves application configuration from different sources
#[derive(Debug)]
pub struct AppConfig<ConfigExt = Empty> {
    /// host address where to start Application
    pub host: IpAddr,
    /// When serialized uses `<PREFIX>`_PORT or `<PREFIX>`_SERVER_PORT names.
    /// `<PREFIX>`_SERVER_PORT has higher priority.
    pub port: u16,
    /// configuration for logs and traces
    pub observability_cfg: ObservabilityConfig,
    /// configures management endpoints
    pub management_cfg: ManagementConfig,
    /// TLS configuration parameters
    #[cfg(feature = "tls")]
    pub tls: TlsConfigurationVariables,
    /// field for each application specific configuration
    pub private: ConfigExt,
    /// Why it is here read more: [`https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.WorkerGuard.html`]
    /// This one will not be cloned and will be set to [`None`] in clone.
    pub worker_guard: Option<WorkerGuard>,
}

impl<ConfigExt> Clone for AppConfig<ConfigExt>
where
    ConfigExt: Clone,
{
    fn clone(&self) -> Self {
        Self {
            host: self.host,
            port: self.port,
            observability_cfg: self.observability_cfg.clone(),
            management_cfg: self.management_cfg.clone(),
            #[cfg(feature = "tls")]
            tls: self.tls.clone(),
            private: self.private.clone(),
            worker_guard: None,
        }
    }
}

impl<'de, ConfigExt> Deserialize<'de> for AppConfig<ConfigExt>
where
    ConfigExt: Debug + DeserializeOwned,
{
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let config = Value::deserialize(deserializer)?;
        let host = config.pointer_and_deserialize(HOST_PTR)?;
        let port = config
            .pointer_and_deserialize(PORT_SERVER_PTR)
            .or_else(|_err: D::Error| config.pointer_and_deserialize(PORT_PTR))?;

        let management_cfg = config
            .pointer_and_deserialize::<_, D::Error>(MANAGEMENT_PTR)
            .unwrap_or_default();
        let observability_cfg = ObservabilityConfig::deserialize(&config).map_err(Error::custom)?;
        #[cfg(feature = "tls")]
        let tls = TlsConfigurationVariables::deserialize(&config).map_err(Error::custom)?;
        let private = ConfigExt::deserialize(config).map_err(Error::custom)?;

        Ok(AppConfig::<ConfigExt> {
            host,
            port,
            observability_cfg,
            management_cfg,
            #[cfg(feature = "tls")]
            tls,
            private,
            worker_guard: None,
        })
    }
}

impl Default for AppConfig {
    #[allow(clippy::expect_used)]
    fn default() -> Self {
        AppConfig::builder()
            .add_default()
            .add_env_prefixed("OTEL")
            .build()
            .expect("Default config never fails")
    }
}

impl<ConfigExt> AppConfig<ConfigExt> {
    /// Creates [`AppConfigBuilder`] to add different sources to config
    pub fn builder() -> AppConfigBuilder<ConfigExt> {
        AppConfigBuilder::new()
    }

    /// Load file by given path and add environment variables with given prefix in addition to default config
    ///
    /// Environment variables have highet priority then file and then default configuration
    pub fn default_with(file_path: &str, env_prefix: &str) -> Result<Self>
    where
        ConfigExt: Debug + DeserializeOwned,
    {
        AppConfig::builder()
            .add_default()
            .add_env_prefixed("OTEL")
            .add_file(file_path)
            .add_env_prefixed(env_prefix)
            .build()
    }

    /// Load configuration from provided container with [`ConfigSource`] which override default config.
    pub fn load_from<'a, S>(sources: S) -> Result<Self>
    where
        ConfigExt: Debug + DeserializeOwned,
        S: IntoIterator<Item = ConfigSource<'a>>,
    {
        let mut config_builder = AppConfig::<ConfigExt>::builder()
            .add_default()
            .add_env_prefixed("OTEL");

        for source in sources {
            config_builder = match source {
                ConfigSource::String(str, format) => config_builder.add_str(str, format),
                ConfigSource::File(path) => config_builder.add_file(path),
                ConfigSource::EnvPrefix(prefix) => config_builder.add_env_prefixed(prefix),
            };
        }

        config_builder.build()
    }
}

/// AppConfig builder to set up multiple sources
#[derive(Debug, Default)]
pub struct AppConfigBuilder<ConfigExt> {
    builder: ConfigBuilder<DefaultState>,
    phantom: PhantomData<ConfigExt>,
}

impl<ConfigExt> AppConfigBuilder<ConfigExt> {
    /// Creates new [`AppConfigBuilder`]
    pub fn new() -> Self {
        Self {
            builder: ConfigBuilder::default(),
            phantom: PhantomData,
        }
    }

    /// Reads all registered sources
    pub fn build(self) -> Result<AppConfig<ConfigExt>>
    where
        ConfigExt: Debug + DeserializeOwned,
    {
        Ok(self
            .builder
            .build()?
            .try_deserialize::<AppConfig<ConfigExt>>()?)
    }

    /// Add default config
    #[must_use]
    pub fn add_default(mut self) -> Self {
        self.builder = self
            .builder
            .add_source(File::from_str(DEFAULT_CONFIG, FileFormat::Toml));
        self
    }

    /// Add file
    #[must_use]
    pub fn add_file(mut self, path: &str) -> Self {
        self.builder = self.builder.add_source(File::with_name(path));
        self
    }

    /// Add string
    #[must_use]
    pub fn add_str(mut self, str: &str, format: FileFormat) -> Self {
        self.builder = self.builder.add_source(File::from_str(str, format));
        self
    }

    /// Add environment variables with specified prefix and default separator: "_"
    #[must_use]
    pub fn add_env_prefixed(mut self, prefix: &str) -> Self {
        self.builder = self.builder.add_source(
            Environment::with_prefix(prefix)
                .try_parsing(true)
                .separator(DEFAULT_SEPARATOR),
        );
        self
    }
}