1use std::pin::Pin;
2use std::sync::Arc;
3
4use crate::Result;
5
6pub trait HealthCheck: Send + Sync + 'static {
15 fn check(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>;
21}
22
23struct FnHealthCheck<F>(F);
25
26impl<F, Fut> HealthCheck for FnHealthCheck<F>
27where
28 F: Fn() -> Fut + Send + Sync + 'static,
29 Fut: Future<Output = Result<()>> + Send + 'static,
30{
31 fn check(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
32 Box::pin((self.0)())
33 }
34}
35
36pub struct HealthChecks {
52 checks: Vec<(String, Arc<dyn HealthCheck>)>,
53}
54
55impl HealthChecks {
56 pub fn new() -> Self {
58 Self { checks: Vec::new() }
59 }
60
61 pub fn check(mut self, name: &str, c: impl HealthCheck) -> Self {
63 self.checks.push((name.to_owned(), Arc::new(c)));
64 self
65 }
66
67 pub fn check_fn<F, Fut>(mut self, name: &str, f: F) -> Self
71 where
72 F: Fn() -> Fut + Send + Sync + 'static,
73 Fut: Future<Output = Result<()>> + Send + 'static,
74 {
75 self.checks
76 .push((name.to_owned(), Arc::new(FnHealthCheck(f))));
77 self
78 }
79
80 pub(crate) fn as_slice(&self) -> &[(String, Arc<dyn HealthCheck>)] {
82 &self.checks
83 }
84}
85
86impl Default for HealthChecks {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl HealthCheck for crate::db::Database {
94 fn check(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
95 Box::pin(async {
96 self.conn()
97 .query("SELECT 1", ())
98 .await
99 .map_err(|e| crate::Error::internal("db health check failed").chain(e))?;
100 Ok(())
101 })
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[tokio::test]
110 async fn database_health_check() {
111 let config = crate::db::Config {
112 path: ":memory:".to_string(),
113 ..Default::default()
114 };
115 let db = crate::db::connect(&config).await.unwrap();
116 db.check().await.unwrap();
117 }
118
119 #[tokio::test]
120 async fn fn_health_check_succeeds() {
121 let checks = HealthChecks::new().check_fn("ok", || async { Ok(()) });
122 let (_, c) = &checks.as_slice()[0];
123 c.check().await.unwrap();
124 }
125
126 #[tokio::test]
127 async fn fn_health_check_fails() {
128 let checks =
129 HealthChecks::new().check_fn("fail", || async { Err(crate::Error::internal("down")) });
130 let (_, c) = &checks.as_slice()[0];
131 assert!(c.check().await.is_err());
132 }
133
134 #[tokio::test]
135 async fn chaining_preserves_order() {
136 let checks = HealthChecks::new()
137 .check_fn("a", || async { Ok(()) })
138 .check_fn("b", || async { Ok(()) })
139 .check_fn("c", || async { Ok(()) });
140 let names: Vec<&str> = checks.as_slice().iter().map(|(n, _)| n.as_str()).collect();
141 assert_eq!(names, vec!["a", "b", "c"]);
142 }
143
144 #[tokio::test]
145 async fn health_checks_default_is_empty() {
146 let checks = HealthChecks::default();
147 assert!(checks.as_slice().is_empty());
148 }
149}