Functional Weave
Code in Rust

legal.court-deadline@1.0.1

impl/rust.rs

3,464 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::dates_add_days::{add_days, parse_iso_date};  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_bank_holidays::bank_holidays;  ← from dates.bank-holidays ^1.0.0 · built alongside by fune
use super::dates_day_of_week::day_of_week;  ← from dates.day-of-week ^1.0.0 · built alongside by fune

/// A deadline in days by CPR 2.8: clear days, short periods, court-office closures.
///
/// # Panics
/// Panics on a bad date, days below 1, an unknown rule, atCourtOffice outside a
/// within period, closed days without atCourtOffice, or a year without
/// published bank holidays when one is needed.
pub fn court_deadline(
    trigger_date: &str,
    days: i64,
    rule: &str,
    at_court_office: bool,
    court_closed_days: &[String],
) -> CourtDeadline {
    parse_iso_date(trigger_date);
    if days < 1 {
        panic!("days must be an integer of at least 1, received {}", days);
    }
    if rule != "within" && rule != "at-least-after" && rule != "at-least-before" {
        panic!("unknown deadline rule \"{}\": expected within, at-least-after or at-least-before", rule);
    }
    if at_court_office && rule != "within" {
        panic!("atCourtOffice applies only to a within period (CPR 2.8(5))");
    }
    if !at_court_office && !court_closed_days.is_empty() {
        panic!("courtClosedDays only apply when atCourtOffice is true");
    }
    for d in court_closed_days {
        parse_iso_date(d);
    }

    // England and Wales holidays by year, fetched only for the years a count touches.
    let mut holidays: Vec<(i64, Vec<String>)> = Vec::new();
    let mut is_non_working = |iso: &str| -> bool {
        if day_of_week(iso) >= 6 {
            return true;
        }
        let year: i64 = iso[0..4].parse().unwrap();
        if !holidays.iter().any(|(y, _)| *y == year) {
            let dates = bank_holidays("england-and-wales", year).into_iter().map(|h| h.date).collect();
            holidays.push((year, dates));
        }
        holidays.iter().find(|(y, _)| *y == year).unwrap().1.iter().any(|d| d == iso)
    };

    let short_period = days <= 5;
    let step = if rule == "at-least-before" { -1 } else { 1 };
    // Walk the clear days: the trigger day is never one, and in a short period
    // weekends and bank holidays are passed over without counting.
    let mut cursor = trigger_date.to_string();
    let mut counted = 0;
    while counted < days {
        cursor = add_days(&cursor, step);
        if !short_period || !is_non_working(&cursor) {
            counted += 1;
        }
    }
    if rule != "within" {
        // The act or event falls on the day beyond the last clear day.
        return CourtDeadline { date: add_days(&cursor, step), short_period, extended: false };
    }
    let mut date = cursor;
    let mut extended = false;
    if at_court_office {
        while is_non_working(&date) || court_closed_days.iter().any(|d| *d == date) {
            date = add_days(&date, 1);
            extended = true;
        }
    }
    CourtDeadline { date, short_period, extended }
}

pub fn court_deadline_to_value(d: &CourtDeadline) -> Value {
    Value::obj(vec![
        ("date", Value::str(&d.date)),
        ("shortPeriod", Value::Bool(d.short_period)),
        ("extended", Value::Bool(d.extended)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let closed: Vec<String> = args[4].as_arr().iter().map(|v| v.as_str().to_string()).collect();
    court_deadline_to_value(&court_deadline(
        args[0].as_str(),
        args[1].as_i64(),
        args[2].as_str(),
        args[3].as_bool(),
        &closed,
    ))
}