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