use crate::app::context::AppContext;
use crate::error::RoadsterResult;
use crate::health::check::HealthCheck;
use crate::health::check::default::default_health_checks;
use std::collections::BTreeMap;
use std::sync::Arc;
use thiserror::Error;
use tracing::info;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HealthCheckRegistryError {
#[error("The provided `HealthCheck` was already registered: `{0}`")]
AlreadyRegistered(String),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error + Send + Sync>),
}
pub struct HealthCheckRegistry {
health_checks: BTreeMap<String, Arc<dyn HealthCheck>>,
}
impl HealthCheckRegistry {
pub(crate) fn new(context: &AppContext) -> Self {
Self {
health_checks: default_health_checks(context),
}
}
pub fn register<H>(&mut self, health_check: H) -> RoadsterResult<()>
where
H: HealthCheck + 'static,
{
self.register_arc(Arc::new(health_check))
}
pub fn register_arc(&mut self, health_check: Arc<dyn HealthCheck>) -> RoadsterResult<()> {
let name = health_check.name();
if !health_check.enabled() {
info!(health_check.name=%name, "Health check is not enabled, skipping registration");
return Ok(());
}
info!(health_check.name=%name, "Registering health check");
if self
.health_checks
.insert(name.clone(), health_check)
.is_some()
{
return Err(HealthCheckRegistryError::AlreadyRegistered(name).into());
}
Ok(())
}
pub fn checks(&self) -> Vec<Arc<dyn HealthCheck>> {
self.health_checks.values().cloned().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::AppConfig;
use crate::health::check::MockHealthCheck;
use rstest::rstest;
#[rstest]
#[case(true, 1)]
#[case(false, 0)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn register_check(#[case] check_enabled: bool, #[case] expected_count: usize) {
let mut config = AppConfig::test(None).unwrap();
config.health_check.default_enable = false;
let context = AppContext::test(Some(config), None, None).unwrap();
let mut check: MockHealthCheck = MockHealthCheck::default();
check.expect_enabled().return_const(check_enabled);
check.expect_name().return_const("test".to_string());
let mut subject: HealthCheckRegistry = HealthCheckRegistry::new(&context);
subject.register(check).unwrap();
assert_eq!(subject.checks().len(), expected_count);
assert_eq!(
subject.checks().iter().any(|check| check.name() == "test"),
check_enabled
);
}
}