use super::parse::{act, ask};
use super::{Context, args};
use crate::boundary::codec::spend::{CEILING, PRICE, PRICES};
use crate::boundary::{Action, Gesture, Query};
use crate::spend::{Price, decimal, parse_usd};
pub const PRICE_USAGE: &str = "/price <provider> <model> <input> <output> [<cache_read> [<cache_write>]] | /price <provider> <model> off";
pub const CEILING_USAGE: &str = "/ceiling <usd> | /ceiling off";
const OFF: &str = "off";
pub(super) fn read(verb: &str, tail: &str, ctx: &Context) -> Result<Gesture, String> {
match verb {
PRICES => args::none(tail, verb).map(|()| ask(Query::Prices)),
PRICE => price(tail),
CEILING => ceiling(tail),
_ => super::queries::queries(verb, tail, ctx),
}
}
fn price(tail: &str) -> Result<Gesture, String> {
let words: Vec<&str> = tail.split_whitespace().collect();
let (provider, model, rates) = match words.as_slice() {
[provider, model, word] if *word == OFF => (*provider, *model, None),
[provider, model, rates @ ..] if (2..=4).contains(&rates.len()) => {
let mut micro = [0u64; 4];
for (slot, word) in micro.iter_mut().zip(rates) {
*slot = figure(word, PRICE)?;
}
let [input, output, cache_read, cache_write] = micro;
(
*provider,
*model,
Some(Price {
input,
output,
cache_read,
cache_write,
}),
)
}
_ => return Err(format!("/{PRICE}: usage: {PRICE_USAGE}")),
};
Ok(act(Action::Price {
provider: provider.to_owned(),
model: model.to_owned(),
rates,
}))
}
fn ceiling(tail: &str) -> Result<Gesture, String> {
let micro_usd = match args::optional_word(tail, CEILING)?.as_deref() {
Some(OFF) => None,
Some(word) => Some(figure(word, CEILING)?),
None => return Err(format!("/{CEILING}: usage: {CEILING_USAGE}")),
};
Ok(act(Action::Ceiling { micro_usd }))
}
fn figure(word: &str, verb: &str) -> Result<u64, String> {
parse_usd(word).ok_or_else(|| format!("/{verb}: {word:?} is not a non-negative USD figure"))
}
pub(super) fn spell(action: &Action) -> String {
match action {
Action::Price {
provider,
model,
rates: Some(rates),
} => format!(
"/{PRICE} {provider} {model} {} {} {} {}",
decimal(rates.input),
decimal(rates.output),
decimal(rates.cache_read),
decimal(rates.cache_write)
),
Action::Price {
provider, model, ..
} => format!("/{PRICE} {provider} {model} {OFF}"),
Action::Ceiling {
micro_usd: Some(micro),
} => format!("/{CEILING} {}", decimal(*micro)),
_ => format!("/{CEILING} {OFF}"),
}
}