Functional Weave
Code in Rust

subscriptions.mrr-arr@1.0.0

impl/rust.rs

2,957 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_rational::{add_rational, multiply_rational, rational, rational_to_integer};  ← from math.rational ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value};  ← from money.amount ^1.0.0 · built alongside by fune

/// MRR and ARR from active and past-due subscriptions. Every charge becomes an
/// exact annual amount first, and each total is rounded once at the end, so a
/// list of annual plans does not lose a penny per plan.
///
/// # Panics
/// Panics on a currency mismatch, a negative amount, an interval count below
/// 1, or an unknown interval or status.
pub fn mrr_arr(subscriptions: &[RecurringCharge], currency: &str) -> MrrArr {
    let zero = money(0, currency);
    let mut annual = rational(0, 1);
    let mut counted = 0;
    let mut excluded = 0;
    for s in subscriptions {
        assert_same_currency(&zero, &s.amount);
        if s.amount.minor < 0 {
            panic!("recurring amount must not be negative, received {}", s.amount.minor);
        }
        if s.interval_count < 1 {
            panic!("interval count must be 1 or more, received {}", s.interval_count);
        }
        let per_year: i64 = match s.interval.as_str() {
            "day" => 365,
            "week" => 52,
            "month" => 12,
            "quarter" => 4,
            "year" => 1,
            other => panic!(
                "unknown billing interval \"{}\": expected day, week, month, quarter or year",
                other
            ),
        };
        match s.status.as_str() {
            "active" | "past-due" => {
                counted += 1;
                annual = add_rational(&annual, &rational(s.amount.minor * per_year, s.interval_count));
            }
            "trialing" | "paused" | "canceled" => excluded += 1,
            other => panic!(
                "unknown subscription status \"{}\": expected active, past-due, trialing, paused or canceled",
                other
            ),
        }
    }
    MrrArr {
        mrr: money(rational_to_integer(&multiply_rational(&annual, &rational(1, 12)), "half-up"), currency),
        arr: money(rational_to_integer(&annual, "half-up"), currency),
        counted,
        excluded,
    }
}

pub fn recurring_charge_from_value(v: &Value) -> RecurringCharge {
    RecurringCharge {
        amount: money_from_value(v.get("amount")),
        interval: v.get("interval").as_str().to_string(),
        interval_count: v.get("intervalCount").as_i64(),
        status: v.get("status").as_str().to_string(),
    }
}

pub fn mrr_arr_to_value(r: &MrrArr) -> Value {
    Value::obj(vec![
        ("mrr", money_to_value(&r.mrr)),
        ("arr", money_to_value(&r.arr)),
        ("counted", Value::Int(r.counted)),
        ("excluded", Value::Int(r.excluded)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let subs: Vec<RecurringCharge> = args[0].as_arr().iter().map(recurring_charge_from_value).collect();
    mrr_arr_to_value(&mrr_arr(&subs, args[1].as_str()))
}