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 = "_";
#[derive(Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
pub struct Empty {}
#[derive(Debug)]
pub struct AppConfig<ConfigExt = Empty> {
pub host: IpAddr,
pub port: u16,
pub observability_cfg: ObservabilityConfig,
pub management_cfg: ManagementConfig,
#[cfg(feature = "tls")]
pub tls: TlsConfigurationVariables,
pub private: ConfigExt,
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> {
pub fn builder() -> AppConfigBuilder<ConfigExt> {
AppConfigBuilder::new()
}
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()
}
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()
}
}
#[derive(Debug, Default)]
pub struct AppConfigBuilder<ConfigExt> {
builder: ConfigBuilder<DefaultState>,
phantom: PhantomData<ConfigExt>,
}
impl<ConfigExt> AppConfigBuilder<ConfigExt> {
pub fn new() -> Self {
Self {
builder: ConfigBuilder::default(),
phantom: PhantomData,
}
}
pub fn build(self) -> Result<AppConfig<ConfigExt>>
where
ConfigExt: Debug + DeserializeOwned,
{
Ok(self
.builder
.build()?
.try_deserialize::<AppConfig<ConfigExt>>()?)
}
#[must_use]
pub fn add_default(mut self) -> Self {
self.builder = self
.builder
.add_source(File::from_str(DEFAULT_CONFIG, FileFormat::Toml));
self
}
#[must_use]
pub fn add_file(mut self, path: &str) -> Self {
self.builder = self.builder.add_source(File::with_name(path));
self
}
#[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
}
#[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
}
}