use super::{Lookup, Model, Posted};
use crate::reply::prices::Prices;
use crate::ui::Channel;
use crate::verbs::prices::{bound, rates};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Table {
pub channel: Channel,
pub prices: Prices,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Pricing {
pub tables: Vec<Table>,
pub provider: String,
pub model: String,
pub rates: String,
pub bound: String,
}
pub const NEEDS_ROW: &str = "name a provider and a model to price";
pub const NEEDS_BOUND: &str = "type a ceiling in USD, or off, to set one";
impl Model {
pub fn begin_prices(&mut self) {
self.lookup = Some(Lookup::Pricing);
self.outbox
.push(Posted::read(crate::verbs::prices::prices()));
if let Some(aim) = &self.aim {
self.outbox
.push(Posted::read(crate::verbs::providers(aim.address.clone())));
}
}
pub fn pricing(&self) -> bool {
self.lookup == Some(Lookup::Pricing)
}
pub(super) fn priced(&mut self, channel: &Channel, prices: Prices) {
let answered = Table {
channel: channel.clone(),
prices,
};
let tables = &mut self.pricing.tables;
match tables
.iter_mut()
.find(|held| held.channel.name == channel.name)
{
Some(held) => *held = answered,
None => tables.push(answered),
}
}
pub fn unpriceable(&self, channel: &Channel) -> Option<String> {
let draft = &self.pricing;
if draft.provider.trim().is_empty() || draft.model.trim().is_empty() {
return Some(NEEDS_ROW.to_owned());
}
let words: Vec<&str> = draft.rates.split_whitespace().collect();
if let Err(why) = rates(&words) {
return Some(why);
}
let aim = self
.aim
.as_ref()
.filter(|aim| aim.channel == channel.name)?;
let roster = self.providers.as_ref()?;
let provider = draft.provider.trim();
(!roster.iter().any(|row| row.name == provider)).then(|| {
format!(
"{} has no provider row {provider:?} — the engine would refuse it",
aim.address
)
})
}
pub fn post_price(&mut self, channel: &Channel) {
if self.unpriceable(channel).is_some() {
return;
}
let draft = &self.pricing;
let words: Vec<&str> = draft.rates.split_whitespace().collect();
let envelope = crate::verbs::price(
draft.provider.trim().to_owned(),
draft.model.trim().to_owned(),
rates(&words).unwrap_or_default(),
);
self.outbox
.push(Posted::act(envelope).down(channel.clone()));
}
pub fn post_unprice(&mut self, channel: &Channel, provider: &str, model: &str) {
let envelope = crate::verbs::price(provider.to_owned(), model.to_owned(), None);
self.outbox
.push(Posted::act(envelope).down(channel.clone()));
}
pub fn unbounded(&self) -> Option<String> {
let typed = self.pricing.bound.trim();
if typed.is_empty() {
return Some(NEEDS_BOUND.to_owned());
}
bound(typed).err()
}
pub fn post_ceiling(&mut self, channel: &Channel) {
let Ok(usd) = bound(self.pricing.bound.trim()) else {
return;
};
self.outbox
.push(Posted::act(crate::verbs::ceiling(usd)).down(channel.clone()));
}
pub fn post_lift(&mut self, channel: &Channel) {
self.outbox
.push(Posted::act(crate::verbs::ceiling(None)).down(channel.clone()));
}
}
#[cfg(test)]
mod tests;