rovenue/cache/
placements.rs

1use rusqlite::params;
2
3use crate::error::RovenueResult;
4
5use super::CacheStore;
6
7/// Persistent store for the last-known placement response, mirroring
8/// [`OfferingsCacheRepo`](super::offerings::OfferingsCacheRepo). The cached
9/// `body` is the raw `PlacementsResponse` JSON (the `data` payload), keyed by
10/// `placement:{identifier}` so it can be parsed back and re-resolved (bucket
11/// draw + variant selection) when the network is unavailable.
12pub struct PlacementsCacheRepo<'a> {
13    store: &'a CacheStore,
14}
15
16impl<'a> PlacementsCacheRepo<'a> {
17    pub fn new(store: &'a CacheStore) -> Self {
18        Self { store }
19    }
20
21    pub fn get(&self, resource: &str) -> RovenueResult<Option<String>> {
22        self.store.with_conn(|c| {
23            let mut stmt = c.prepare("SELECT body FROM placements_cache WHERE resource = ?1")?;
24            let mut rows = stmt.query(params![resource])?;
25            if let Some(r) = rows.next()? {
26                Ok(Some(r.get::<_, String>(0)?))
27            } else {
28                Ok(None)
29            }
30        })
31    }
32
33    pub fn put(&self, resource: &str, body: &str, updated_at_ms: u64) -> RovenueResult<()> {
34        self.store.with_conn(|c| {
35            c.execute(
36                "INSERT INTO placements_cache (resource, body, updated_at_ms)
37                 VALUES (?1, ?2, ?3)
38                 ON CONFLICT(resource) DO UPDATE SET
39                   body = excluded.body,
40                   updated_at_ms = excluded.updated_at_ms",
41                params![resource, body, updated_at_ms as i64],
42            )?;
43            Ok(())
44        })
45    }
46}