Skip to content

Commit

Permalink
feat: Add SmtpHealthCheck (#396)
Browse files Browse the repository at this point in the history
  • Loading branch information
spencewenski authored Oct 9, 2024
1 parent c880805 commit c860236
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 3 deletions.
2 changes: 1 addition & 1 deletion examples/full/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ rusty-sidekiq = { workspace = true, default-features = false }
serde = { workspace = true, features = ["derive"] }

# Email
lettre = { workspace = true }
lettre = { workspace = true, features = ["pool"] }

[build-dependencies]
tonic-build = { workspace = true }
Expand Down
33 changes: 32 additions & 1 deletion src/api/core/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::error::RoadsterResult;
use crate::health_check::{CheckResponse, ErrorData, HealthCheck, Status};
#[cfg(feature = "open-api")]
use aide::OperationIo;
#[cfg(feature = "sidekiq")]
#[cfg(any(feature = "sidekiq", feature = "email-smtp"))]
use anyhow::anyhow;
use axum::extract::FromRef;
use futures::future::join_all;
Expand Down Expand Up @@ -135,6 +135,37 @@ async fn ping_db(db: &DatabaseConnection, duration: Option<Duration>) -> Roadste
Ok(())
}

#[cfg(feature = "email-smtp")]
pub(crate) async fn smtp_health(context: &AppContext, duration: Option<Duration>) -> CheckResponse {
let timer = Instant::now();
let status = match ping_smtp(context.mailer(), duration).await {
Ok(_) => Status::Ok,
Err(err) => Status::Err(ErrorData::builder().msg(err.to_string()).build()),
};
let timer = timer.elapsed();
CheckResponse::builder()
.status(status)
.latency(timer)
.build()
}

#[cfg(feature = "email-smtp")]
async fn ping_smtp(
mailer: &lettre::SmtpTransport,
duration: Option<Duration>,
) -> RoadsterResult<()> {
let connected = if let Some(duration) = duration {
timeout(duration, async { mailer.test_connection() }).await??
} else {
mailer.test_connection()?
};
if connected {
Ok(())
} else {
Err(anyhow!("Not connected to the SMTP server").into())
}
}

#[cfg(feature = "sidekiq")]
#[instrument(skip_all)]
pub(crate) async fn redis_health(
Expand Down
2 changes: 2 additions & 0 deletions src/config/health_check/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ cli = 10000
[health-check.database]

[health-check.sidekiq]

[health-check.smtp]
4 changes: 4 additions & 0 deletions src/config/health_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub struct HealthCheck {
#[validate(nested)]
pub sidekiq: HealthCheckConfig<()>,

#[cfg(feature = "email-smtp")]
#[validate(nested)]
pub smtp: HealthCheckConfig<()>,

/// Allows providing configs for custom health checks. Any configs that aren't pre-defined above
/// will be collected here.
///
Expand Down
2 changes: 2 additions & 0 deletions src/config/snapshots/roadster__config__tests__test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ cli = 10000

[health-check.sidekiq]

[health-check.smtp]

[service]
default-enable = true

Expand Down
8 changes: 7 additions & 1 deletion src/health_check/default.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::app::context::AppContext;
#[cfg(feature = "db-sql")]
use crate::health_check::database::DatabaseHealthCheck;
#[cfg(feature = "email-smtp")]
use crate::health_check::email::smtp::SmtpHealthCheck;
#[cfg(feature = "sidekiq")]
use crate::health_check::sidekiq_enqueue::SidekiqEnqueueHealthCheck;
#[cfg(feature = "sidekiq")]
Expand All @@ -25,6 +27,10 @@ pub fn default_health_checks(
Arc::new(SidekiqFetchHealthCheck {
context: context.clone(),
}),
#[cfg(feature = "email-smtp")]
Arc::new(SmtpHealthCheck {
context: context.clone(),
}),
];

health_checks
Expand All @@ -34,7 +40,7 @@ pub fn default_health_checks(
.collect()
}

#[cfg(all(test, feature = "sidekiq", feature = "db-sql",))]
#[cfg(all(test, feature = "sidekiq", feature = "db-sql", feature = "email-smtp"))]
mod tests {
use crate::app::context::AppContext;
use crate::config::AppConfig;
Expand Down
2 changes: 2 additions & 0 deletions src/health_check/email/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(feature = "email-smtp")]
pub mod smtp;
57 changes: 57 additions & 0 deletions src/health_check/email/smtp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use crate::api::core::health::smtp_health;
use crate::app::context::AppContext;
use crate::error::RoadsterResult;
use crate::health_check::{CheckResponse, HealthCheck};
use async_trait::async_trait;
use tracing::instrument;

pub struct SmtpHealthCheck {
pub(crate) context: AppContext,
}

#[async_trait]
impl HealthCheck for SmtpHealthCheck {
fn name(&self) -> String {
"smtp".to_string()
}

fn enabled(&self) -> bool {
enabled(&self.context)
}

#[instrument(skip_all)]
async fn check(&self) -> RoadsterResult<CheckResponse> {
Ok(smtp_health(&self.context, None).await)
}
}

fn enabled(context: &AppContext) -> bool {
context.config().health_check.smtp.common.enabled(context)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::config::AppConfig;
use rstest::rstest;

#[rstest]
#[case(false, Some(true), true)]
#[case(false, Some(false), false)]
#[cfg_attr(coverage_nightly, coverage(off))]
fn enabled(
#[case] default_enable: bool,
#[case] enable: Option<bool>,
#[case] expected_enabled: bool,
) {
// Arrange
let mut config = AppConfig::test(None).unwrap();
config.health_check.default_enable = default_enable;
config.health_check.smtp.common.enable = enable;

let context = AppContext::test(Some(config), None, None).unwrap();

// Act/Assert
assert_eq!(super::enabled(&context), expected_enabled);
}
}
2 changes: 2 additions & 0 deletions src/health_check/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#[cfg(feature = "db-sql")]
pub mod database;
pub mod default;
#[cfg(feature = "email")]
pub mod email;
pub mod registry;
#[cfg(feature = "sidekiq")]
pub mod sidekiq_enqueue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ expression: health_checks
'db',
'sidekiq-enqueue',
'sidekiq-fetch',
'smtp',
]

0 comments on commit c860236

Please sign in to comment.