forge_workspace/store/
pricing.rs1use std::time::SystemTime;
9
10use anyhow::Context;
11use redb::TableDefinition;
12use serde::{Deserialize, Serialize};
13
14use super::Db;
15
16const PRICING_CACHE: TableDefinition<&str, &[u8]> = TableDefinition::new("pricing_cache");
17
18const KEY: &str = "litellm";
20
21#[derive(Debug, PartialEq, Serialize, Deserialize)]
23pub struct CachedPricing {
24 pub fetched_at: SystemTime,
25 pub json: String,
26}
27
28pub fn load(db: &Db) -> anyhow::Result<Option<CachedPricing>> {
31 let txn = db.database().begin_read()?;
32 let table = match txn.open_table(PRICING_CACHE) {
33 Ok(t) => t,
34 Err(redb::TableError::TableDoesNotExist(_)) => return Ok(None),
35 Err(e) => return Err(e.into()),
36 };
37 match table.get(KEY)? {
38 Some(value) => {
39 Ok(Some(serde_json::from_slice(value.value()).context("decode cached pricing")?))
40 }
41 None => Ok(None),
42 }
43}
44
45pub fn store(db: &Db, entry: &CachedPricing) -> anyhow::Result<()> {
47 let value = serde_json::to_vec(entry).context("serialize cached pricing")?;
48 let txn = db.database().begin_write()?;
49 {
50 let mut table = txn.open_table(PRICING_CACHE)?;
51 table.insert(KEY, value.as_slice())?;
52 }
53 txn.commit()?;
54 Ok(())
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use std::time::Duration;
61 use tempfile::tempdir;
62
63 fn open_db() -> (tempfile::TempDir, Db) {
64 let dir = tempdir().expect("tempdir");
65 let db = Db::open(&dir.path().join("db.redb")).expect("open db");
66 (dir, db)
67 }
68
69 #[test]
70 fn load_is_none_before_any_fetch() {
71 let (_dir, db) = open_db();
72 assert!(load(&db).expect("load").is_none());
73 }
74
75 #[test]
76 fn store_then_load_round_trips_and_overwrites() {
77 let (_dir, db) = open_db();
78 let first = CachedPricing {
79 fetched_at: SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000),
80 json: r#"{"m":{}}"#.to_owned(),
81 };
82 store(&db, &first).expect("store");
83 assert_eq!(load(&db).expect("load"), Some(first));
84
85 let second = CachedPricing {
86 fetched_at: SystemTime::UNIX_EPOCH + Duration::from_secs(1_800_000_000),
87 json: r#"{"n":{}}"#.to_owned(),
88 };
89 store(&db, &second).expect("store again");
90 assert_eq!(load(&db).expect("load").expect("present").json, r#"{"n":{}}"#);
91 }
92
93 #[test]
94 fn load_surfaces_a_corrupt_blob_as_error() {
95 let (_dir, db) = open_db();
96 let txn = db.database().begin_write().expect("begin");
97 {
98 let mut table = txn.open_table(PRICING_CACHE).expect("open table");
99 table.insert(KEY, b"not json".as_slice()).expect("insert corrupt");
100 }
101 txn.commit().expect("commit");
102 assert!(load(&db).is_err(), "a corrupt row surfaces as an error");
105 }
106}