Skip to main content

polyoxide_clob/api/
health.rs

1use polyoxide_core::HttpClient;
2use serde::{Deserialize, Serialize};
3use std::time::{Duration, Instant};
4
5use crate::{
6    error::ClobError,
7    request::{AuthMode, Request},
8};
9
10/// Health namespace for API health and latency operations
11#[derive(Clone)]
12pub struct Health {
13    pub(crate) http_client: HttpClient,
14    pub(crate) chain_id: u64,
15}
16
17impl Health {
18    /// Measure the round-trip time (RTT) to the Polymarket CLOB API.
19    ///
20    /// Makes a GET request to the API root and returns the latency.
21    ///
22    /// # Example
23    ///
24    /// ```no_run
25    /// use polyoxide_clob::Clob;
26    ///
27    /// # async fn example() -> Result<(), polyoxide_clob::ClobError> {
28    /// let client = Clob::public();
29    /// let latency = client.health().ping().await?;
30    /// println!("API latency: {}ms", latency.as_millis());
31    /// # Ok(())
32    /// # }
33    /// ```
34    pub async fn ping(&self) -> Result<Duration, ClobError> {
35        let start = Instant::now();
36        let response = self
37            .http_client
38            .client
39            .get(self.http_client.base_url.clone())
40            .send()
41            .await?;
42        let latency = start.elapsed();
43
44        if !response.status().is_success() {
45            return Err(ClobError::from_response(response).await);
46        }
47
48        Ok(latency)
49    }
50
51    /// Get the current server time
52    pub fn server_time(&self) -> Request<ServerTimeResponse> {
53        Request::get(
54            self.http_client.clone(),
55            "/time",
56            AuthMode::None,
57            self.chain_id,
58        )
59    }
60}
61
62/// Response from the server time endpoint.
63///
64/// The live API returns a bare integer (e.g. `1700000000`), not a JSON object.
65/// This type handles that format via a custom `Deserialize` implementation.
66#[derive(Debug, Clone, Serialize)]
67pub struct ServerTimeResponse {
68    pub time: i64,
69}
70
71impl<'de> Deserialize<'de> for ServerTimeResponse {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: serde::Deserializer<'de>,
75    {
76        let time = i64::deserialize(deserializer)?;
77        Ok(ServerTimeResponse { time })
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn server_time_response_deserializes() {
87        let json = "1700000000";
88        let resp: ServerTimeResponse = serde_json::from_str(json).unwrap();
89        assert_eq!(resp.time, 1700000000);
90    }
91
92    #[test]
93    fn server_time_response_rejects_json_object() {
94        let json = r#"{"time": 1700000000}"#;
95        assert!(serde_json::from_str::<ServerTimeResponse>(json).is_err());
96    }
97
98    #[test]
99    fn server_time_response_rejects_string() {
100        let json = r#""1700000000""#;
101        assert!(serde_json::from_str::<ServerTimeResponse>(json).is_err());
102    }
103}