Skip to main content

cachekit/backend/
cachekitio_ttl.rs

1//! [`TtlInspectable`] implementation for the cachekit.io HTTP backend.
2
3use std::time::Duration;
4
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7
8use super::cachekitio::{reqwest_err_sanitized, CachekitIO};
9use super::TtlInspectable;
10use crate::error::BackendError;
11
12#[derive(Deserialize)]
13#[serde(deny_unknown_fields)]
14struct TtlResponse {
15    ttl: Option<u64>,
16}
17
18#[derive(Serialize)]
19struct RefreshTtlRequest {
20    ttl: u64,
21}
22
23#[cfg(not(target_arch = "wasm32"))]
24#[cfg_attr(not(feature = "unsync"), async_trait)]
25#[cfg_attr(feature = "unsync", async_trait(?Send))]
26impl TtlInspectable for CachekitIO {
27    async fn ttl(&self, key: &str) -> Result<Option<Duration>, BackendError> {
28        let url = format!(
29            "{}/v1/cache/{}/ttl",
30            self.api_url(),
31            urlencoding::encode(key)
32        );
33
34        let req =
35            self.with_standard_headers(self.client().get(&url).bearer_auth(self.api_key_str()));
36
37        let resp = req
38            .send()
39            .await
40            .map_err(|e| reqwest_err_sanitized(e, self.api_key_str()))?;
41
42        match resp.status().as_u16() {
43            200 => {
44                let body: TtlResponse = resp.json().await.map_err(|e| {
45                    BackendError::transient(format!("failed to parse TTL response: {e}"))
46                })?;
47                Ok(body.ttl.map(Duration::from_secs))
48            }
49            404 => Ok(None),
50            _ => Err(self.error_from_response(resp).await),
51        }
52    }
53
54    async fn refresh_ttl(&self, key: &str, ttl: Duration) -> Result<bool, BackendError> {
55        let secs = ttl.as_secs();
56        if secs == 0 {
57            return Err(BackendError::permanent(
58                "refresh_ttl requires at least 1 second".to_string(),
59            ));
60        }
61
62        let url = format!(
63            "{}/v1/cache/{}/ttl",
64            self.api_url(),
65            urlencoding::encode(key)
66        );
67
68        let body = serde_json::to_vec(&RefreshTtlRequest { ttl: secs }).map_err(|e| {
69            BackendError::permanent(format!("failed to serialize refresh_ttl request: {e}"))
70        })?;
71
72        let req = self.with_standard_headers(
73            self.client()
74                .patch(&url)
75                .bearer_auth(self.api_key_str())
76                .header("Content-Type", "application/json")
77                .body(body),
78        );
79
80        let resp = req
81            .send()
82            .await
83            .map_err(|e| reqwest_err_sanitized(e, self.api_key_str()))?;
84
85        match resp.status().as_u16() {
86            200 | 204 => Ok(true),
87            404 => Ok(false),
88            _ => Err(self.error_from_response(resp).await),
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    /// Compile-time proof that CachekitIO implements TtlInspectable.
98    fn _assert_ttl_inspectable(_b: &dyn TtlInspectable) {}
99
100    #[test]
101    fn cachekitio_is_ttl_inspectable() {
102        fn _check(backend: &CachekitIO) {
103            _assert_ttl_inspectable(backend);
104        }
105    }
106}