use std::collections::BTreeMap;
use serde_json::{Map, Value};
mod parse;
pub use parse::{MICRO_PER_CENT, MICRO_PER_USD, Price};
pub(crate) use parse::{decimal, parse_usd, quoted};
pub const ANY: &str = "*";
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Prices(BTreeMap<String, BTreeMap<String, Price>>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PriceRow {
pub provider: String,
pub model: String,
pub rates: Price,
}
impl Prices {
pub fn from_json(value: &Value) -> Self {
let Some(rows) = value.as_object() else {
return Self::default();
};
Self(
rows.iter()
.filter_map(|(provider, row)| {
let models: BTreeMap<String, Price> = row
.as_object()?
.iter()
.filter_map(|(model, rate)| Some((model.clone(), Price::from_json(rate)?)))
.collect();
(!models.is_empty()).then(|| (provider.clone(), models))
})
.collect(),
)
}
pub fn to_json(&self) -> Value {
let rows: Map<String, Value> = self
.0
.iter()
.map(|(provider, models)| {
let row: Map<String, Value> = models
.iter()
.map(|(model, rate)| (model.clone(), Value::Object(rate.json_fields())))
.collect();
(provider.clone(), Value::Object(row))
})
.collect();
Value::Object(rows)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn of(&self, provider: Option<&str>, model: Option<&str>) -> Option<Price> {
let model = model?;
let row = |models: &BTreeMap<String, Price>| {
models.get(model).or_else(|| models.get(ANY)).copied()
};
if let Some(provider) = provider {
return row(self.0.get(provider)?);
}
let mut candidates = self.0.values().filter_map(row);
match (candidates.next(), candidates.next()) {
(Some(price), None) => Some(price),
_ => None,
}
}
pub fn rows(&self) -> Vec<PriceRow> {
self.0
.iter()
.flat_map(|(provider, models)| {
models.iter().map(|(model, rates)| PriceRow {
provider: provider.clone(),
model: model.clone(),
rates: *rates,
})
})
.collect()
}
pub fn set(&mut self, provider: &str, model: &str, rates: Option<Price>) {
match rates {
Some(rates) => {
self.0
.entry(provider.to_owned())
.or_default()
.insert(model.to_owned(), rates);
}
None => {
if let Some(models) = self.0.get_mut(provider) {
models.remove(model);
if models.is_empty() {
self.0.remove(provider);
}
}
}
}
}
}