Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::units_convert::convert_units; ← from units.convert ^1.0.0 · built alongside by fune
// Each basis is a units.convert symbol and how many of it the price is for.
fn basis(per: &str) -> (&'static str, i128) {
match per {
"kg" => ("kg", 1),
"100g" => ("g", 100),
"10g" => ("g", 10),
"L" => ("L", 1),
"100mL" => ("mL", 100),
"10mL" => ("mL", 10),
"75cL" => ("mL", 750),
"m" => ("m", 1),
"m2" => ("m2", 1),
"m3" => ("m3", 1),
"item" => ("item", 1),
other => panic!("unknown unit price basis \"{}\"", other),
}
}
fn is_decimal(value: &str) -> bool {
let mut parts = value.splitn(2, '.');
let digits = |s: &str| !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit());
digits(parts.next().unwrap_or("")) && parts.next().map_or(true, digits)
}
/// The price of one `per` of the product, rounded half up to the minor unit.
///
/// The quantity is converted exactly (units.convert, 12 decimal places), then
/// price x multiplier / quantity is done in integers, so the one rounding step
/// is the last one.
///
/// # Panics
/// Panics on a negative price, a zero or malformed quantity, an item quantity
/// with a measured basis or the other way round, or units that do not convert.
pub fn unit_price(price: &Money, quantity: &str, unit: &str, per: &str) -> Money {
if price.minor < 0 {
panic!("price must not be negative, received {}", price.minor);
}
let (target, multiplier) = basis(per);
let converted: String = if target == "item" || unit == "item" {
if target != "item" {
panic!("a quantity in items can only be priced per \"item\"");
}
if unit != "item" {
panic!("per \"item\" needs the quantity in \"item\"");
}
if !is_decimal(quantity) {
panic!("quantity must be more than 0, received \"{}\"", quantity);
}
quantity.to_string()
} else {
convert_units(quantity, unit, target, 12)
};
if converted.starts_with('-') || !converted.bytes().any(|b| (b'1'..=b'9').contains(&b)) {
panic!("quantity must be more than 0, received \"{}\"", quantity);
}
// converted = digits / 10^places, so price / converted = price x 10^places / digits.
let mut parts = converted.splitn(2, '.');
let whole = parts.next().unwrap_or("");
let fraction = parts.next().unwrap_or("");
let digits: i128 = format!("{}{}", whole, fraction).parse().unwrap();
let numerator = (price.minor as i128) * multiplier * 10i128.pow(fraction.len() as u32);
let mut quotient = numerator / digits;
if (numerator % digits) * 2 >= digits {
quotient += 1;
}
money(quotient as i64, &price.currency)
}
pub fn fune_vector(args: &[Value]) -> Value {
money_to_value(&unit_price(
&money_from_value(&args[0]),
args[1].as_str(),
args[2].as_str(),
args[3].as_str(),
))
}