Skip to main content

clawdentity_core/db/
verify_cache.rs

1use rusqlite::{OptionalExtension, params};
2
3use crate::db::{SqliteStore, now_utc_ms};
4use crate::error::{CoreError, Result};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct VerifyCacheEntry {
8    pub cache_key: String,
9    pub registry_url: String,
10    pub fetched_at_ms: i64,
11    pub payload_json: String,
12}
13
14/// TODO(clawdentity): document `upsert_verify_cache_entry`.
15pub fn upsert_verify_cache_entry(
16    store: &SqliteStore,
17    cache_key: &str,
18    registry_url: &str,
19    payload_json: &str,
20) -> Result<()> {
21    let cache_key = cache_key.trim();
22    let registry_url = registry_url.trim();
23    let payload_json = payload_json.trim();
24    if cache_key.is_empty() {
25        return Err(CoreError::InvalidInput("cache_key is required".to_string()));
26    }
27    if registry_url.is_empty() {
28        return Err(CoreError::InvalidInput(
29            "registry_url is required".to_string(),
30        ));
31    }
32    if payload_json.is_empty() {
33        return Err(CoreError::InvalidInput(
34            "payload_json is required".to_string(),
35        ));
36    }
37
38    let fetched_at_ms = now_utc_ms();
39    store.with_connection(|connection| {
40        connection.execute(
41            "INSERT INTO verify_cache (cache_key, registry_url, fetched_at_ms, payload_json)
42             VALUES (?1, ?2, ?3, ?4)
43             ON CONFLICT(cache_key) DO UPDATE SET
44                registry_url = excluded.registry_url,
45                fetched_at_ms = excluded.fetched_at_ms,
46                payload_json = excluded.payload_json",
47            params![cache_key, registry_url, fetched_at_ms, payload_json],
48        )?;
49        Ok(())
50    })
51}
52
53/// TODO(clawdentity): document `get_verify_cache_entry`.
54pub fn get_verify_cache_entry(
55    store: &SqliteStore,
56    cache_key: &str,
57) -> Result<Option<VerifyCacheEntry>> {
58    let cache_key = cache_key.trim();
59    if cache_key.is_empty() {
60        return Ok(None);
61    }
62
63    store.with_connection(|connection| {
64        let mut statement = connection.prepare(
65            "SELECT cache_key, registry_url, fetched_at_ms, payload_json
66             FROM verify_cache
67             WHERE cache_key = ?1",
68        )?;
69        let result = statement
70            .query_row([cache_key], |row| {
71                Ok(VerifyCacheEntry {
72                    cache_key: row.get(0)?,
73                    registry_url: row.get(1)?,
74                    fetched_at_ms: row.get(2)?,
75                    payload_json: row.get(3)?,
76                })
77            })
78            .optional()?;
79        Ok(result)
80    })
81}
82
83/// TODO(clawdentity): document `delete_verify_cache_entry`.
84pub fn delete_verify_cache_entry(store: &SqliteStore, cache_key: &str) -> Result<bool> {
85    let cache_key = cache_key.trim();
86    if cache_key.is_empty() {
87        return Ok(false);
88    }
89    store.with_connection(|connection| {
90        let deleted =
91            connection.execute("DELETE FROM verify_cache WHERE cache_key = ?1", [cache_key])?;
92        Ok(deleted > 0)
93    })
94}
95
96/// TODO(clawdentity): document `purge_verify_cache_before`.
97pub fn purge_verify_cache_before(store: &SqliteStore, cutoff_ms: i64) -> Result<usize> {
98    store.with_connection(|connection| {
99        let deleted = connection.execute(
100            "DELETE FROM verify_cache WHERE fetched_at_ms < ?1",
101            [cutoff_ms],
102        )?;
103        Ok(deleted)
104    })
105}
106
107#[cfg(test)]
108mod tests {
109    use tempfile::TempDir;
110
111    use crate::db::SqliteStore;
112
113    use super::{
114        delete_verify_cache_entry, get_verify_cache_entry, purge_verify_cache_before,
115        upsert_verify_cache_entry,
116    };
117
118    #[test]
119    fn upsert_get_delete_verify_cache_entry() {
120        let temp = TempDir::new().expect("temp dir");
121        let store = SqliteStore::open_path(temp.path().join("db.sqlite3")).expect("open db");
122
123        upsert_verify_cache_entry(
124            &store,
125            "keys::https://registry.clawdentity.com",
126            "https://registry.clawdentity.com",
127            "{\"keys\":[]}",
128        )
129        .expect("upsert");
130
131        let entry = get_verify_cache_entry(&store, "keys::https://registry.clawdentity.com")
132            .expect("get")
133            .expect("entry");
134        assert_eq!(entry.registry_url, "https://registry.clawdentity.com");
135        assert_eq!(entry.payload_json, "{\"keys\":[]}");
136
137        let purged = purge_verify_cache_before(&store, i64::MAX).expect("purge");
138        assert_eq!(purged, 1);
139        assert!(
140            get_verify_cache_entry(&store, "keys::https://registry.clawdentity.com")
141                .expect("get after purge")
142                .is_none()
143        );
144
145        let deleted = delete_verify_cache_entry(&store, "keys::https://registry.clawdentity.com")
146            .expect("delete");
147        assert!(!deleted);
148    }
149}