Skip to main content

kcode_k1_accounting/
lib.rs

1use std::{
2    collections::BTreeMap,
3    sync::{Arc, Mutex, MutexGuard},
4};
5
6use rust_decimal::Decimal;
7
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9pub struct UsageValue {
10    pub units: Decimal,
11    pub price_cents: Decimal,
12}
13
14#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct AccountingEvent {
16    pub source: String,
17    pub operation: String,
18    pub usage: BTreeMap<String, UsageValue>,
19}
20
21#[derive(Clone, Default)]
22pub struct Accounting {
23    ledger: Arc<Mutex<Ledger>>,
24}
25
26#[derive(Default)]
27struct Ledger {
28    events: Vec<Arc<AccountingEvent>>,
29}
30
31impl Accounting {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn record(&self, event: &AccountingEvent) {
37        let event = Arc::new(event.clone());
38        self.lock().events.push(event);
39    }
40
41    pub fn entries(&self) -> Vec<AccountingEvent> {
42        let events = self.lock().events.clone();
43        events.into_iter().map(|event| (*event).clone()).collect()
44    }
45
46    pub fn totals(&self) -> BTreeMap<String, UsageValue> {
47        let events = self.lock().events.clone();
48        let mut totals: BTreeMap<String, UsageValue> = BTreeMap::new();
49
50        for event in events {
51            for (key, value) in &event.usage {
52                let total = totals.entry(key.clone()).or_default();
53                total.units += value.units;
54                total.price_cents += value.price_cents;
55            }
56        }
57
58        totals
59    }
60
61    fn lock(&self) -> MutexGuard<'_, Ledger> {
62        self.ledger
63            .lock()
64            .unwrap_or_else(|poisoned| poisoned.into_inner())
65    }
66}