use serde_json::{Map, Value};
use super::fields;
use super::steps::Spend;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Figure {
pub tokens: Spend,
pub cost: Option<Cost>,
pub attribution: Attribution,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribution {
pub kind: String,
pub label: Option<String>,
}
pub(crate) fn figure(value: &Value) -> Result<Figure, String> {
let obj: &Map<String, Value> = value.as_object().ok_or("spend: not an object")?;
Ok(Figure {
tokens: super::steps::spend(obj)?,
cost: money(obj)?,
attribution: attribution(obj)?,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cost {
pub usd: String,
pub unpriced_tokens: u64,
}
impl Cost {
pub fn said(&self) -> String {
if self.unpriced_tokens > 0 {
format!("at least {}", self.usd)
} else {
self.usd.clone()
}
}
}
pub fn priced(count: String, cost: Option<&Cost>) -> String {
match cost {
Some(cost) => format!("{count} — {}", cost.said()),
None => count,
}
}
pub(crate) fn money(obj: &Map<String, Value>) -> Result<Option<Cost>, String> {
let Some(usd) = fields::opt_text(obj, "usd")? else {
return Ok(None);
};
Ok(Some(Cost {
usd,
unpriced_tokens: fields::opt_count(obj, "unpriced_tokens")?.unwrap_or(0),
}))
}
pub(crate) fn cost(obj: &Map<String, Value>, key: &str) -> Result<Option<Cost>, String> {
fields::nested(obj, key, |held| {
money(held)?.ok_or_else(|| format!("field {key:?}: missing \"usd\""))
})
}
fn attribution(obj: &Map<String, Value>) -> Result<Attribution, String> {
let held = obj
.get("attribution")
.and_then(Value::as_object)
.ok_or("missing or non-object field \"attribution\"")?;
Ok(Attribution {
kind: fields::text(held, "kind")?,
label: fields::opt_text(held, "label")?,
})
}
#[cfg(test)]
mod tests;