Skip to main content

cachekit/backend/
cachekitio_lock.rs

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