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