Functional Weave
Code in Rust

education.school-year-group@1.0.0

impl/rust.rs

2,212 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::parse_iso_date;  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::education_school_year_group_data::{COHORT_RULES, YEAR_GROUPS};  ← this capability’s own data, compiled from data/cohort-rules.json into the same file by fune build

/// The year group a child is in on a date, placed by their birth date.
///
/// The cohort comes from the birth date itself (born on or after the cohort
/// start joins a year later), never from an age on a cut-off date, which
/// misplaces 29 February births where the cohort starts on 1 March.
///
/// # Panics
/// Panics on a malformed date, onDate before birthDate, or an unknown nation.
pub fn school_year_group(birth_date: &str, on_date: &str, nation: &str) -> SchoolYearGroup {
    let birth = parse_iso_date(birth_date);
    let on = parse_iso_date(on_date);
    if on_date < birth_date {
        panic!("onDate {} is before birthDate {}", on_date, birth_date);
    }
    let rule = match COHORT_RULES.iter().find(|r| r.nation == nation) {
        Some(r) => r,
        None => panic!("unknown nation \"{}\": use england, wales, scotland or northern-ireland", nation),
    };
    let entry_year = birth.year + rule.entry_age + if &birth_date[5..] >= rule.cohort_start { 1 } else { 0 };
    let start_year = if &on_date[5..] >= rule.school_year_start { on.year } else { on.year - 1 };
    let index = start_year - entry_year;
    let name = YEAR_GROUPS.iter().find(|g| g.nation == nation && g.index == index);
    SchoolYearGroup {
        school_year_start: format!("{:04}-{}", start_year, rule.school_year_start),
        entry_year,
        index,
        year_group: name.map(|g| g.label.to_string()),
        stage: name.map(|g| g.stage.to_string()),
    }
}

pub fn school_year_group_to_value(g: &SchoolYearGroup) -> Value {
    let opt = |v: &Option<String>| match v {
        Some(s) => Value::str(s),
        None => Value::Null,
    };
    Value::obj(vec![
        ("schoolYearStart", Value::str(&g.school_year_start)),
        ("entryYear", Value::Int(g.entry_year)),
        ("index", Value::Int(g.index)),
        ("yearGroup", opt(&g.year_group)),
        ("stage", opt(&g.stage)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    school_year_group_to_value(&school_year_group(args[0].as_str(), args[1].as_str(), args[2].as_str()))
}