Functional Weave
Code in TypeScript

property.rent-review@1.0.0

impl/rust.rs

3,971 bytes · the Rust implementation · view raw

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::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

const MAX_RENT: i64 = 1_000_000_000;
const MAX_INDEX: i64 = 1_000_000;

fn check_basis_points(name: &str, value: Option<i64>) {
    if let Some(v) = value {
        if v <= -10000 || v > 100000 {
            panic!("{} must be an integer from -9999 to 100000, received {}", name, v);
        }
    }
}

/// The rent after an index-linked review, held between an optional collar and
/// cap, and not below the passing rent when upward-only. Every comparison is
/// on exact fractions and the rent is rounded half-up once.
///
/// # Panics
/// Panics on a rent or index out of range, a collar or cap out of range, or a
/// collar above the cap.
pub fn rent_review(
    passing_rent: &Money,
    base_index: i64,
    review_index: i64,
    collar_basis_points: Option<i64>,
    cap_basis_points: Option<i64>,
    upward_only: bool,
) -> RentReviewResult {
    if passing_rent.minor < 0 || passing_rent.minor > MAX_RENT {
        panic!("passingRent must be between 0 and {} minor units, received {}", MAX_RENT, passing_rent.minor);
    }
    for (name, value) in [("baseIndex", base_index), ("reviewIndex", review_index)] {
        if value <= 0 || value > MAX_INDEX {
            panic!("{} must be an integer from 1 to {}, received {}", name, MAX_INDEX, value);
        }
    }
    check_basis_points("collarBasisPoints", collar_basis_points);
    check_basis_points("capBasisPoints", cap_basis_points);
    if let (Some(collar), Some(cap)) = (collar_basis_points, cap_basis_points) {
        if collar > cap {
            panic!("collar {} is above cap {}", collar, cap);
        }
    }
    let p = passing_rent.minor;
    // The multiplier as a fraction num/den: the index ratio, or 1 + collar/cap.
    let (mut num, mut den, mut rule) = (review_index, base_index, "index");
    match (cap_basis_points, collar_basis_points) {
        (Some(cap), _) if review_index * 10000 > base_index * (10000 + cap) => {
            num = 10000 + cap;
            den = 10000;
            rule = "cap";
        }
        (_, Some(collar)) if review_index * 10000 < base_index * (10000 + collar) => {
            num = 10000 + collar;
            den = 10000;
            rule = "collar";
        }
        _ => {}
    }
    if upward_only && num < den {
        num = 1;
        den = 1;
        rule = "upward-only";
    }
    RentReviewResult {
        new_rent: money(round_div(p * num, den, "half-up"), &passing_rent.currency),
        index_rent: money(round_div(p * review_index, base_index, "half-up"), &passing_rent.currency),
        index_change_basis_points: round_div((review_index - base_index) * 10000, base_index, "half-up"),
        applied_by: rule.to_string(),
    }
}

pub fn rent_review_result_to_value(result: &RentReviewResult) -> Value {
    Value::obj(vec![
        ("newRent", money_to_value(&result.new_rent)),
        ("indexRent", money_to_value(&result.index_rent)),
        ("indexChangeBasisPoints", Value::Int(result.index_change_basis_points)),
        ("appliedBy", Value::str(&result.applied_by)),
    ])
}

fn optional_int(value: &Value, name: &str) -> Option<i64> {
    match value {
        Value::Null => None,
        Value::Int(v) => Some(*v),
        _ => panic!("{} must be an integer from -9999 to 100000, received {:?}", name, value),
    }
}

fn int_arg(value: &Value, name: &str) -> i64 {
    match value {
        Value::Int(v) => *v,
        _ => panic!("{} must be an integer from 1 to {}, received {:?}", name, MAX_INDEX, value),
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    rent_review_result_to_value(&rent_review(
        &money_from_value(&args[0]),
        int_arg(&args[1], "baseIndex"),
        int_arg(&args[2], "reviewIndex"),
        optional_int(&args[3], "collarBasisPoints"),
        optional_int(&args[4], "capBasisPoints"),
        args[5].as_bool(),
    ))
}