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::dates_add_days::epoch_day_from_iso; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_to_value}; ← from money.amount ^1.0.0 · built alongside by fune
use super::money_sum::sum_money; ← from money.sum ^1.0.0 · built alongside by fune
use super::telecoms_roaming_charge_data::{RoamingRate, ROAMING_TARIFFS, ROAMING_TARIFFS_HISTORY}; ← this capability’s own data, compiled from data/roaming-tariffs.json into the same file by fune build
fn is_country(s: &str) -> bool {
s.len() == 2 && s.bytes().all(|b| b.is_ascii_uppercase())
}
/// Price roaming usage in one country on one date: find the tariff's zone for
/// the country in force on that date, then multiply each kind of usage by its
/// price. Exact integer arithmetic throughout.
///
/// # Panics
/// Panics on a malformed country or date, negative usage, or when no zone of
/// the tariff covers the country on that date.
pub fn roaming_charge(tariff: &str, country: &str, on_date: &str, usage: &RoamingUsage) -> RoamingCharge {
if !is_country(country) {
panic!(
"country must be an ISO 3166-1 alpha-2 code in capitals, received \"{}\"",
country
);
}
epoch_day_from_iso(on_date);
let counts = [usage.days, usage.data_mb, usage.call_out_minutes, usage.call_in_minutes, usage.sms_out];
if counts.iter().any(|&n| n < 0) {
panic!("usage must be whole numbers of 0 or more");
}
let covers = |rule: &RoamingRate| rule.tariff == tariff && rule.countries.split(' ').any(|c| c == country);
let mut best: Option<&RoamingRate> = None;
for rule in ROAMING_TARIFFS {
if !covers(rule) || on_date < rule.valid_from {
continue;
}
if let Some(valid_to) = rule.valid_to {
if on_date > valid_to {
continue;
}
}
if best.map_or(true, |b| rule.valid_from > b.valid_from) {
best = Some(rule);
}
}
let best = match best {
Some(rule) => rule,
None => {
// A history=current build has dropped the rows that ended;
// answering an older date with today's prices would be wrong, so
// say what happened.
if ROAMING_TARIFFS_HISTORY != "full" {
let mut earliest: Option<&'static str> = None;
for rule in ROAMING_TARIFFS {
if covers(rule) && earliest.map_or(true, |e| rule.valid_from < e) {
earliest = Some(rule.valid_from);
}
}
if let Some(earliest) = earliest {
if on_date < earliest {
panic!(
"no roaming zone for {} in tariff {} on {}: this build was installed with history={}, so it only carries rules from {}. Reinstall with history=full to price older usage.",
country, tariff, on_date, ROAMING_TARIFFS_HISTORY, earliest
);
}
}
}
panic!("no roaming zone for {} in tariff {} on {}", country, tariff, on_date)
}
};
let c = best.currency;
let daily_fees = money(usage.days * best.daily_fee_minor, c);
let data = money(usage.data_mb * best.data_per_mb_minor, c);
let calls = money(
usage.call_out_minutes * best.call_out_per_minute_minor + usage.call_in_minutes * best.call_in_per_minute_minor,
c,
);
let sms = money(usage.sms_out * best.sms_out_minor, c);
let total = sum_money(&[daily_fees.clone(), data.clone(), calls.clone(), sms.clone()], c);
RoamingCharge {
tariff: tariff.to_string(),
zone: best.zone.to_string(),
daily_fees,
data,
calls,
sms,
total,
}
}
pub fn roaming_usage_from_value(v: &Value) -> RoamingUsage {
RoamingUsage {
days: v.get("days").as_i64(),
data_mb: v.get("dataMb").as_i64(),
call_out_minutes: v.get("callOutMinutes").as_i64(),
call_in_minutes: v.get("callInMinutes").as_i64(),
sms_out: v.get("smsOut").as_i64(),
}
}
pub fn roaming_charge_to_value(r: &RoamingCharge) -> Value {
Value::obj(vec![
("tariff", Value::str(&r.tariff)),
("zone", Value::str(&r.zone)),
("dailyFees", money_to_value(&r.daily_fees)),
("data", money_to_value(&r.data)),
("calls", money_to_value(&r.calls)),
("sms", money_to_value(&r.sms)),
("total", money_to_value(&r.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
roaming_charge_to_value(&roaming_charge(
args[0].as_str(),
args[1].as_str(),
args[2].as_str(),
&roaming_usage_from_value(&args[3]),
))
}