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; ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_add_months::add_months; ← from dates.add-months ^1.0.0 · built alongside by fune
use super::dates_days_between::days_between; ← from dates.days-between ^1.0.0 · built alongside by fune
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
fn whole_number(name: &str, value: i64) {
if value < 0 {
panic!("{} must be a whole number, 0 or more, received {}", name, value);
}
}
/// The next service, by time or mileage, whichever comes first.
///
/// "Whichever comes first" needs a date for the mileage limit too, so it is
/// projected from the average daily mileage since the last service, rounded up
/// to a whole day: a service booked for the projected day is never late on
/// that estimate.
///
/// # Panics
/// Panics on a negative number, no interval at all, a malformed date, or an
/// onDate or mileage before the last service.
pub fn service_due(
last_service_date: &str,
last_service_mileage: i64,
interval_months: i64,
interval_miles: i64,
on_date: &str,
current_mileage: i64,
) -> ServiceDue {
whole_number("lastServiceMileage", last_service_mileage);
whole_number("intervalMonths", interval_months);
whole_number("intervalMiles", interval_miles);
whole_number("currentMileage", current_mileage);
if interval_months == 0 && interval_miles == 0 {
panic!("at least one of intervalMonths and intervalMiles must be above zero");
}
let days_since = days_between(last_service_date, on_date);
if days_since < 0 {
panic!("onDate {} is before the last service on {}", on_date, last_service_date);
}
let miles_since = current_mileage - last_service_mileage;
if miles_since < 0 {
panic!(
"currentMileage {} is below the mileage at the last service, {}",
current_mileage, last_service_mileage
);
}
let due_date = if interval_months > 0 { Some(add_months(last_service_date, interval_months)) } else { None };
let days_remaining = due_date.as_ref().map(|d| days_between(on_date, d));
let due_mileage = if interval_miles > 0 { Some(last_service_mileage + interval_miles) } else { None };
let miles_remaining = due_mileage.map(|m| m - current_mileage);
let mut projected: Option<String> = None;
let mut mileage_date: Option<String> = None;
if let Some(remaining) = miles_remaining {
if remaining <= 0 {
mileage_date = Some(on_date.to_string());
} else if days_since > 0 && miles_since > 0 {
let date = add_days(on_date, round_div(remaining * days_since, miles_since, "up"));
projected = Some(date.clone());
mileage_date = Some(date);
}
}
let mut next_due = due_date.clone();
let mut due_by: Option<String> = due_date.as_ref().map(|_| "date".to_string());
if let Some(md) = mileage_date {
if next_due.as_ref().map_or(true, |n| md < *n) {
next_due = Some(md);
due_by = Some("mileage".to_string());
}
}
ServiceDue {
overdue: days_remaining.map_or(false, |d| d < 0) || miles_remaining.map_or(false, |m| m < 0),
due_date,
due_mileage,
days_remaining,
miles_remaining,
projected_mileage_date: projected,
next_due_date: next_due,
due_by,
}
}
fn opt_str(v: &Option<String>) -> Value {
match v {
Some(s) => Value::str(s),
None => Value::Null,
}
}
fn opt_int(v: Option<i64>) -> Value {
match v {
Some(i) => Value::Int(i),
None => Value::Null,
}
}
pub fn service_due_to_value(s: &ServiceDue) -> Value {
Value::obj(vec![
("dueDate", opt_str(&s.due_date)),
("dueMileage", opt_int(s.due_mileage)),
("daysRemaining", opt_int(s.days_remaining)),
("milesRemaining", opt_int(s.miles_remaining)),
("projectedMileageDate", opt_str(&s.projected_mileage_date)),
("nextDueDate", opt_str(&s.next_due_date)),
("dueBy", opt_str(&s.due_by)),
("overdue", Value::Bool(s.overdue)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
for (i, name) in [(1usize, "lastServiceMileage"), (2, "intervalMonths"), (3, "intervalMiles"), (5, "currentMileage")] {
if let Value::Float(f) = &args[i] {
panic!("{} must be a whole number, 0 or more, received {}", name, f);
}
}
service_due_to_value(&service_due(
args[0].as_str(),
args[1].as_i64(),
args[2].as_i64(),
args[3].as_i64(),
args[4].as_str(),
args[5].as_i64(),
))
}