Functional Weave
Code in Python

charity.donation-matching@1.0.0

impl/rust.rs

3,122 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::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate;  ← from money.apply-rate ^1.0.0 · built alongside by fune

fn check(name: &str, currency: &str, amount: &Money) {
    if amount.currency != currency {
        panic!("currency mismatch: {} and {}", amount.currency, currency);
    }
    if amount.minor < 0 {
        panic!("{} must not be negative, received {}", name, amount.minor);
    }
}

/// An employer's matched-giving contribution. The ratio is applied first and
/// rounded down; then the match is held to whatever room is left under the
/// donor's cap and the programme's budget, never below zero.
///
/// # Panics
/// Panics on a negative amount or ratio, or mixed currencies.
pub fn donation_match(
    donation: &Money,
    ratio_basis_points: i64,
    donor_cap: Option<&Money>,
    donor_matched_so_far: &Money,
    programme_cap: Option<&Money>,
    programme_matched_so_far: &Money,
) -> DonationMatch {
    let currency = donation.currency.as_str();
    check("donation", currency, donation);
    if ratio_basis_points < 0 {
        panic!("ratioBasisPoints must be a non-negative integer, received {}", ratio_basis_points);
    }
    if let Some(cap) = donor_cap {
        check("donorCap", currency, cap);
    }
    check("donorMatchedSoFar", currency, donor_matched_so_far);
    if let Some(cap) = programme_cap {
        check("programmeCap", currency, cap);
    }
    check("programmeMatchedSoFar", currency, programme_matched_so_far);

    let uncapped = apply_rate(donation, ratio_basis_points, "down").minor;
    let mut matched = uncapped;
    let mut capped_by = "none";
    if let Some(cap) = donor_cap {
        let room = (cap.minor - donor_matched_so_far.minor).max(0);
        if room < matched {
            matched = room;
            capped_by = "donor";
        }
    }
    if let Some(cap) = programme_cap {
        let room = (cap.minor - programme_matched_so_far.minor).max(0);
        if room < matched {
            matched = room;
            capped_by = "programme";
        }
    }
    DonationMatch {
        matched: money(matched, currency),
        uncapped: money(uncapped, currency),
        capped_by: capped_by.to_string(),
    }
}

pub fn donation_match_to_value(m: &DonationMatch) -> Value {
    Value::obj(vec![
        ("matched", money_to_value(&m.matched)),
        ("uncapped", money_to_value(&m.uncapped)),
        ("cappedBy", Value::str(&m.capped_by)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = args[1] {
        if f.fract() != 0.0 {
            panic!("ratioBasisPoints must be a non-negative integer, received {}", f);
        }
    }
    let donor_cap = if args[2].is_null() { None } else { Some(money_from_value(&args[2])) };
    let programme_cap = if args[4].is_null() { None } else { Some(money_from_value(&args[4])) };
    donation_match_to_value(&donation_match(
        &money_from_value(&args[0]),
        args[1].as_i64(),
        donor_cap.as_ref(),
        &money_from_value(&args[3]),
        programme_cap.as_ref(),
        &money_from_value(&args[5]),
    ))
}