1use std::sync::{Arc, Mutex};
4use std::time::Duration;
5
6use http::Method;
7use polyoxide_rt::{Instant, Runtime};
8
9use crate::client::HttpClient;
10use crate::error::ApiError;
11use crate::hooks::{Authenticator, Cost, DynAuthenticator, RequestParts};
12use crate::request::RequestError;
13use crate::response::Response;
14
15#[derive(Debug)]
17#[non_exhaustive]
18pub struct Pong {
19 pub round_trip: Duration,
23 pub response: Response,
25}
26
27struct Stopwatch {
30 runtime: Arc<dyn Runtime>,
31 signed: Mutex<Option<Instant>>,
32}
33
34impl Stopwatch {
35 fn new(runtime: Arc<dyn Runtime>) -> Self {
36 Self {
37 runtime,
38 signed: Mutex::new(None),
39 }
40 }
41
42 fn elapsed(&self) -> Duration {
44 self.signed
45 .lock()
46 .unwrap_or_else(|poisoned| poisoned.into_inner())
47 .map_or(Duration::ZERO, |sent| {
48 self.runtime.now().saturating_duration_since(sent)
49 })
50 }
51}
52
53impl Authenticator for Stopwatch {
54 async fn sign(&self, _parts: &mut RequestParts, _attempt: u32) -> Result<(), ApiError> {
55 *self
56 .signed
57 .lock()
58 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(self.runtime.now());
59 Ok(())
60 }
61}
62
63impl HttpClient {
64 pub async fn health<E: RequestError>(&self, path: &str, costs: &[Cost]) -> Result<Pong, E> {
76 let stopwatch = Stopwatch::new(Arc::clone(&self.runtime));
77 let response = self
78 .send(
79 RequestParts::new(Method::GET, path),
80 costs,
81 Some(DynAuthenticator::from_ref(&stopwatch)),
82 )
83 .await?;
84 let round_trip = stopwatch.elapsed();
85
86 if !response.status().is_success() {
88 return Err(ApiError::from_response(response).await.into());
89 }
90
91 Ok(Pong {
92 round_trip,
93 response,
94 })
95 }
96}