use serde_json::{Map, Value, json};
use crate::spend::{Price, decimal, quoted};
use super::{Action, str_of};
pub(crate) const PRICES: &str = "prices";
pub(crate) const PRICE: &str = "price";
pub(crate) const CEILING: &str = "ceiling";
const RATES: [&str; 4] = ["input", "output", "cache_read", "cache_write"];
pub(super) fn encode(action: &Action) -> Value {
let mut map = Map::new();
if let Action::Price {
provider,
model,
rates,
} = action
{
map.insert("op".to_owned(), json!(PRICE));
map.insert("provider".to_owned(), json!(provider));
map.insert("model".to_owned(), json!(model));
if let Some(rates) = rates {
map.insert("rates".to_owned(), Value::Object(rates.json_fields()));
}
} else {
map.insert("op".to_owned(), json!(CEILING));
if let Action::Ceiling {
micro_usd: Some(micro),
} = action
{
map.insert("usd".to_owned(), decimal(*micro));
}
}
Value::Object(map)
}
pub(super) fn decode(op: &str, o: &Map<String, Value>) -> Result<Action, String> {
if op == PRICE {
return Ok(Action::Price {
provider: str_of(o, "provider")?,
model: str_of(o, "model")?,
rates: match o.get("rates") {
None | Some(Value::Null) => None,
Some(rates) => Some(rates_of(rates)?),
},
});
}
Ok(Action::Ceiling {
micro_usd: match o.get("usd") {
None | Some(Value::Null) => None,
Some(usd) => Some(usd_of(usd, "usd")?),
},
})
}
pub(crate) fn rates_of(v: &Value) -> Result<Price, String> {
let o = v.as_object().ok_or("rates: not an object")?;
let mut micro = [0u64; 4];
for (slot, key) in micro.iter_mut().zip(RATES) {
if let Some(rate) = o.get(key) {
*slot = usd_of(rate, key)?;
}
}
let [input, output, cache_read, cache_write] = micro;
Ok(Price {
input,
output,
cache_read,
cache_write,
})
}
pub(crate) fn usd_of(v: &Value, key: &str) -> Result<u64, String> {
quoted(Some(v)).ok_or_else(|| format!("{key}: {v} is not a non-negative USD figure"))
}