1use std::sync::Mutex;
5
6use aspero_core::{Error, ProviderId, Usage};
7use jiff::Timestamp;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11#[non_exhaustive]
12pub struct Budget {
13 pub remaining: Option<f64>,
14 pub resets_at: Option<Timestamp>,
15 pub seen_at: Timestamp,
17}
18
19#[derive(Debug, Default)]
20pub(crate) struct Budgets(Mutex<Vec<(ProviderId, Budget)>>);
21
22impl Budgets {
23 pub(crate) fn note(
25 &self,
26 provider: &ProviderId,
27 usage: Option<&Usage>,
28 error: Option<&Error>,
29 now: Timestamp,
30 ) {
31 let (remaining, resets_at) = match (usage, error) {
32 (Some(u), _) => (u.remaining, u.resets_at),
33 (None, Some(Error::QuotaExhausted { resets_at })) => (Some(0.0), *resets_at),
34 _ => return,
35 };
36 if remaining.is_none() && resets_at.is_none() {
37 return;
38 }
39 let budget = Budget {
40 remaining,
41 resets_at,
42 seen_at: now,
43 };
44 let mut all = self.0.lock().unwrap();
45 match all.iter_mut().find(|(p, _)| p == provider) {
46 Some((_, known)) => *known = budget,
47 None => all.push((provider.clone(), budget)),
48 }
49 }
50
51 pub(crate) fn all(&self) -> Vec<(ProviderId, Budget)> {
52 self.0.lock().unwrap().clone()
53 }
54}