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::property_deposit_cap_data::{DepositCapRule, DEPOSIT_CAPS, DEPOSIT_CAPS_HISTORY}; ← this capability’s own data, compiled from data/deposit-caps.json into the same file by fune build
// Keeps annual rent × 6 exact as a JavaScript number; the same limit here.
const MAX_MINOR: i64 = 1_000_000_000_000;
fn is_iso_date(value: &str) -> bool {
let bytes = value.as_bytes();
bytes.len() == 10
&& bytes.iter().enumerate().all(|(i, b)| if i == 4 || i == 7 { *b == b'-' } else { b.is_ascii_digit() })
}
fn find_rule(jurisdiction: &str, on_date: &str) -> &'static DepositCapRule {
let mut best: Option<&'static DepositCapRule> = None;
let mut earliest: Option<&'static str> = None;
for rule in DEPOSIT_CAPS {
if rule.jurisdiction != jurisdiction {
continue;
}
if earliest.map_or(true, |e| rule.valid_from < e) {
earliest = Some(rule.valid_from);
}
if on_date < rule.valid_from {
continue;
}
if let Some(to) = rule.valid_to {
if on_date > to {
continue;
}
}
if best.map_or(true, |b| rule.valid_from > b.valid_from) {
best = Some(rule);
}
}
if let Some(rule) = best {
return rule;
}
// A build installed with a pruned history must not answer an old date
// with today's rule, so it says what happened instead.
if DEPOSIT_CAPS_HISTORY != "full" {
if let Some(e) = earliest {
if on_date < e {
panic!(
"no deposit cap rule for {} on {}: this build was installed with history={}, so it only carries rules from {}",
jurisdiction, on_date, DEPOSIT_CAPS_HISTORY, e
);
}
}
}
panic!("no deposit cap rule for {} on {}", jurisdiction, on_date)
}
/// The maximum tenancy deposit: five weeks' rent below the annual-rent
/// threshold, six at or above it. The threshold is tested on the exact annual
/// rent, and the cap is annual rent × weeks ÷ 52 rounded down once, so it can
/// never sit a fraction of a penny over the statutory limit.
///
/// # Panics
/// Panics on a malformed date, rent not in GBP or negative, an unknown rent
/// period, or no rule for the jurisdiction on that date.
pub fn deposit_cap(rent: &Money, rent_period: &str, jurisdiction: &str, on_date: &str) -> DepositCap {
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
if rent.currency != "GBP" {
panic!("rent must be in GBP, received {}", rent.currency);
}
if rent.minor < 0 {
panic!("rent must not be negative, received {}", rent.minor);
}
let annual = match rent_period {
"weekly" => rent.minor.checked_mul(52),
"monthly" => rent.minor.checked_mul(12),
"annual" => Some(rent.minor),
other => panic!("unknown rent period \"{}\"", other),
};
let annual = match annual {
Some(a) if a <= MAX_MINOR => a,
_ => panic!("annual rent must be at most {} minor units", MAX_MINOR),
};
let rule = find_rule(jurisdiction, on_date);
let weeks = if annual < rule.annual_rent_threshold_minor { rule.weeks_below } else { rule.weeks_at_or_above };
DepositCap {
annual_rent: money(annual, "GBP"),
weekly_rent: money(annual / 52, "GBP"),
weeks,
maximum: money(annual * weeks / 52, "GBP"),
}
}
pub fn deposit_cap_to_value(cap: &DepositCap) -> Value {
Value::obj(vec![
("annualRent", money_to_value(&cap.annual_rent)),
("weeklyRent", money_to_value(&cap.weekly_rent)),
("weeks", Value::Int(cap.weeks)),
("maximum", money_to_value(&cap.maximum)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
deposit_cap_to_value(&deposit_cap(
&money_from_value(&args[0]),
args[1].as_str(),
args[2].as_str(),
args[3].as_str(),
))
}