use std::{
collections::BTreeMap,
sync::{Arc, Mutex, MutexGuard},
};
use rust_decimal::Decimal;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UsageValue {
pub units: Decimal,
pub price_cents: Decimal,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AccountingEvent {
pub source: String,
pub operation: String,
pub usage: BTreeMap<String, UsageValue>,
}
#[derive(Clone, Default)]
pub struct Accounting {
ledger: Arc<Mutex<Ledger>>,
}
#[derive(Default)]
struct Ledger {
events: Vec<Arc<AccountingEvent>>,
}
impl Accounting {
pub fn new() -> Self {
Self::default()
}
pub fn record(&self, event: &AccountingEvent) {
let event = Arc::new(event.clone());
self.lock().events.push(event);
}
pub fn entries(&self) -> Vec<AccountingEvent> {
let events = self.lock().events.clone();
events.into_iter().map(|event| (*event).clone()).collect()
}
pub fn totals(&self) -> BTreeMap<String, UsageValue> {
let events = self.lock().events.clone();
let mut totals: BTreeMap<String, UsageValue> = BTreeMap::new();
for event in events {
for (key, value) in &event.usage {
let total = totals.entry(key.clone()).or_default();
total.units += value.units;
total.price_cents += value.price_cents;
}
}
totals
}
fn lock(&self) -> MutexGuard<'_, Ledger> {
self.ledger
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}