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 {
65 self.checks.push((name.to_owned(), Arc::new(c)));
66 self
67 }
68
69 pub fn check_fn<F, Fut>(mut self, name: &str, f: F) -> Self
75 where
76 F: Fn() -> Fut + Send + Sync + 'static,
77 Fut: Future<Output = Result<()>> + Send + 'static,
78 {
79 self.checks
80 .push((name.to_owned(), Arc::new(FnHealthCheck(f))));
81 self
82 }
83
84 pub(crate) fn as_slice(&self) -> &[(String, Arc<dyn HealthCheck>)] {
86 &self.checks
87 }
88}
89
90impl Default for HealthChecks {
91 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl HealthCheck for crate::db::Database {
98 fn check(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
99 Box::pin(async {
100 self.conn()
101 .query("SELECT 1", ())
102 .await
103 .map_err(|e| crate::Error::internal("db health check failed").chain(e))?;
104 Ok(())
105 })
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[tokio::test]
114 async fn database_health_check() {
115 let config = crate::db::Config {
116 path: ":memory:".to_string(),
117 ..Default::default()
118 };
119 let db = crate::db::connect(&config).await.unwrap();
120 db.check().await.unwrap();
121 }
122
123 #[tokio::test]
124 async fn fn_health_check_succeeds() {
125 let checks = HealthChecks::new().check_fn("ok", || async { Ok(()) });
126 let (_, c) = &checks.as_slice()[0];
127 c.check().await.unwrap();
128 }
129
130 #[tokio::test]
131 async fn fn_health_check_fails() {
132 let checks =
133 HealthChecks::new().check_fn("fail", || async { Err(crate::Error::internal("down")) });
134 let (_, c) = &checks.as_slice()[0];
135 assert!(c.check().await.is_err());
136 }
137
138 #[tokio::test]
139 async fn chaining_preserves_order() {
140 let checks = HealthChecks::new()
141 .check_fn("a", || async { Ok(()) })
142 .check_fn("b", || async { Ok(()) })
143 .check_fn("c", || async { Ok(()) });
144 let names: Vec<&str> = checks.as_slice().iter().map(|(n, _)| n.as_str()).collect();
145 assert_eq!(names, vec!["a", "b", "c"]);
146 }
147
148 #[tokio::test]
149 async fn health_checks_default_is_empty() {
150 let checks = HealthChecks::default();
151 assert!(checks.as_slice().is_empty());
152 }
153}