Functional Weave
Code in Rust

education.gpa@1.0.0

impl/rust.rs

3,821 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::education_gpa_data::DEFAULT_GRADE_POINTS;  ← this capability’s own data, compiled from data/default-grade-points.json into the same file by fune build
use super::math_round_div::round_div;  ← from math.round-div ^1.0.0 · built alongside by fune

/// Credit-weighted grade point average in hundredths, rounded once.
///
/// Quality points are summed exactly in integers (hundredths x credits), so
/// the rounding mode the caller names is the only rounding that happens.
///
/// # Panics
/// Panics on an empty or duplicated table, a grade not in the table, negative
/// credits or points, or an unknown rounding mode.
pub fn gpa(courses: &[GpaCourse], grade_points: Option<&[GradePoint]>, mode: &str) -> GpaResult {
    let table: Vec<(String, Option<i64>)> = match grade_points {
        Some(rows) => rows.iter().map(|r| (r.grade.clone(), r.points)).collect(),
        None => DEFAULT_GRADE_POINTS.iter().map(|r| (r.grade.to_string(), r.points)).collect(),
    };
    if table.is_empty() {
        panic!("gradePoints must not be empty; pass null for the default table");
    }
    for (i, (grade, points)) in table.iter().enumerate() {
        if table[..i].iter().any(|(g, _)| g == grade) {
            panic!("grade \"{}\" appears twice in gradePoints", grade);
        }
        if let Some(p) = points {
            if *p < 0 {
                panic!("points must be whole hundredths of 0 or more, received {} for grade \"{}\"", p, grade);
            }
        }
    }

    let mut quality_points: i64 = 0;
    let mut gpa_credits: i64 = 0;
    let mut excluded_credits: i64 = 0;
    for course in courses {
        if course.credits < 0 {
            panic!("credits must be a whole number of 0 or more, received {}", course.credits);
        }
        let row = table.iter().find(|(g, _)| *g == course.grade);
        match row {
            None => panic!("grade \"{}\" is not in the grade-point table", course.grade),
            Some((_, None)) => excluded_credits += course.credits,
            Some((_, Some(p))) => {
                quality_points += p * course.credits;
                gpa_credits += course.credits;
            }
        }
    }
    // Validate the mode even when there is nothing to divide.
    round_div(0, 1, mode);
    GpaResult {
        gpa: if gpa_credits == 0 { None } else { Some(round_div(quality_points, gpa_credits, mode)) },
        quality_points,
        gpa_credits,
        excluded_credits,
    }
}

fn whole(value: &Value, message: &str) -> i64 {
    match value {
        Value::Float(f) if f.fract() != 0.0 => panic!("{}, received {}", message, f),
        _ => value.as_i64(),
    }
}

pub fn gpa_result_to_value(r: &GpaResult) -> Value {
    Value::obj(vec![
        ("gpa", match r.gpa { Some(n) => Value::Int(n), None => Value::Null }),
        ("qualityPoints", Value::Int(r.quality_points)),
        ("gpaCredits", Value::Int(r.gpa_credits)),
        ("excludedCredits", Value::Int(r.excluded_credits)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let courses: Vec<GpaCourse> = args[0]
        .as_arr()
        .iter()
        .map(|v| GpaCourse {
            grade: v.get("grade").as_str().to_string(),
            credits: whole(v.get("credits"), "credits must be a whole number of 0 or more"),
        })
        .collect();
    let table: Option<Vec<GradePoint>> = if args[1].is_null() {
        None
    } else {
        Some(
            args[1]
                .as_arr()
                .iter()
                .map(|v| {
                    let p = v.get("points");
                    GradePoint {
                        grade: v.get("grade").as_str().to_string(),
                        points: if p.is_null() { None } else { Some(whole(p, "points must be whole hundredths of 0 or more")) },
                    }
                })
                .collect(),
        )
    };
    gpa_result_to_value(&gpa(&courses, table.as_deref(), args[2].as_str()))
}