use crate::app::context::AppContext;
use crate::config::CustomConfig;
use crate::util::serde::default_true;
use axum_core::extract::FromRef;
use config::{FileFormat, FileSourceString};
use serde_derive::{Deserialize, Serialize};
use serde_with::serde_as;
use std::collections::BTreeMap;
use std::time::Duration;
use validator::Validate;
pub(crate) fn default_config() -> config::File<FileSourceString, FileFormat> {
config::File::from_str(include_str!("default.toml"), FileFormat::Toml)
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct HealthCheck {
#[serde(default = "default_true")]
pub default_enable: bool,
#[validate(nested)]
pub max_duration: MaxDuration,
#[cfg(feature = "db-sql")]
#[validate(nested)]
pub database: HealthCheckConfig<crate::config::EmptyConfig>,
#[cfg(feature = "worker-sidekiq")]
#[validate(nested)]
pub worker_sidekiq: HealthCheckConfig<crate::config::EmptyConfig>,
#[cfg(feature = "worker-pg")]
#[validate(nested)]
pub worker_pg: HealthCheckConfig<crate::config::EmptyConfig>,
#[cfg(feature = "email-smtp")]
#[validate(nested)]
pub smtp: HealthCheckConfig<crate::config::EmptyConfig>,
#[serde(flatten)]
#[validate(nested)]
pub custom: BTreeMap<String, HealthCheckConfig<CustomConfig>>,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct CommonConfig {
#[serde(default)]
pub enable: Option<bool>,
}
impl CommonConfig {
pub fn enabled<S>(&self, state: &S) -> bool
where
S: Clone + Send + Sync + 'static,
AppContext: FromRef<S>,
{
self.enable.unwrap_or(
AppContext::from_ref(state)
.config()
.health_check
.default_enable,
)
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct HealthCheckConfig<T: Validate> {
#[serde(flatten)]
#[validate(nested)]
pub common: CommonConfig,
#[serde(flatten)]
#[validate(nested)]
pub custom: T,
}
#[serde_as]
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Validate, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct MaxDuration {
#[serde_as(as = "serde_with::DurationMilliSeconds")]
pub startup: Duration,
#[serde_as(as = "serde_with::DurationMilliSeconds")]
pub api: Duration,
#[serde_as(as = "serde_with::DurationMilliSeconds")]
pub cli: Duration,
}
#[cfg(test)]
mod tests {
use crate::app::context::AppContext;
use crate::config::AppConfig;
use crate::config::health::check::CommonConfig;
use rstest::rstest;
#[rstest]
#[case(true, None, true)]
#[case(true, Some(true), true)]
#[case(true, Some(false), false)]
#[case(false, None, false)]
#[case(false, Some(true), true)]
#[case(false, Some(false), false)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn common_config_enabled(
#[case] default_enable: bool,
#[case] enable: Option<bool>,
#[case] expected_enabled: bool,
) {
let mut config = AppConfig::test(None).unwrap();
config.health_check.default_enable = default_enable;
let context = AppContext::test(Some(config), None, None).unwrap();
let common_config = CommonConfig { enable };
assert_eq!(common_config.enabled(&context), expected_enabled);
}
}