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()))
}