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
fn state(phase: &str, since: Option<i64>, last: Option<i64>, clear_since: Option<i64>) -> AlertState {
AlertState { phase: phase.to_string(), since, last_notified_at: last, clear_since }
}
fn transition(state: AlertState, notify: Option<&str>, changed: bool) -> AlertTransition {
AlertTransition { state, notify: notify.map(|n| n.to_string()), changed }
}
/// One step of an alert's lifecycle. Pure: the caller stores the returned
/// state and passes it back at the next evaluation.
///
/// Firing needs the condition to hold for_seconds (Prometheus `for`);
/// resolving needs it to stay false clear_for_seconds, so a flapping check
/// does not send resolved-firing-resolved storms. last_notified_at records
/// the last notification of any kind; it drives the repeat interval while
/// firing.
///
/// # Panics
/// Panics on a negative duration, an unknown phase, a pending or firing state
/// without since, or a now earlier than any time in the previous state.
pub fn next_alert_state(previous: &AlertState, condition: bool, now: i64, policy: &AlertPolicy) -> AlertTransition {
let for_seconds = policy.for_seconds;
let clear_for = policy.clear_for_seconds;
if for_seconds < 0 {
panic!("forSeconds must not be negative, received {}", for_seconds);
}
if clear_for < 0 {
panic!("clearForSeconds must not be negative, received {}", clear_for);
}
if let Some(r) = policy.renotify_seconds {
if r < 1 {
panic!("renotifySeconds must be null or at least 1, received {}", r);
}
}
let phase = previous.phase.as_str();
if !matches!(phase, "inactive" | "pending" | "firing" | "resolved") {
panic!("unknown alert phase: {}", phase);
}
let (since, last, clear_since) = (previous.since, previous.last_notified_at, previous.clear_since);
if (phase == "pending" || phase == "firing") && since.is_none() {
panic!("since must be set in phase {}", phase);
}
for (name, t) in [("since", since), ("lastNotifiedAt", last), ("clearSince", clear_since)] {
if let Some(t) = t {
if now < t {
panic!("now {} is earlier than {} {}", now, name, t);
}
}
}
match phase {
"inactive" | "resolved" => {
if !condition {
transition(previous.clone(), None, false)
} else if for_seconds == 0 {
transition(state("firing", Some(now), Some(now), None), Some("firing"), true)
} else {
transition(state("pending", Some(now), last, None), None, true)
}
}
"pending" => {
if !condition {
transition(state("inactive", Some(now), last, None), None, true)
} else if now - since.unwrap() >= for_seconds {
transition(state("firing", Some(now), Some(now), None), Some("firing"), true)
} else {
transition(previous.clone(), None, false)
}
}
_ => {
if condition {
let due = match (policy.renotify_seconds, last) {
(Some(_), None) => true,
(Some(r), Some(l)) => now - l >= r,
(None, _) => false,
};
if due {
transition(state("firing", since, Some(now), None), Some("repeat"), false)
} else {
transition(state("firing", since, last, None), None, false)
}
} else {
let clearing = clear_since.unwrap_or(now);
if now - clearing >= clear_for {
transition(state("resolved", Some(now), Some(now), None), Some("resolved"), true)
} else {
transition(state("firing", since, last, Some(clearing)), None, false)
}
}
}
}
}
fn opt_i64(v: &Value) -> Option<i64> {
if v.is_null() { None } else { Some(v.as_i64()) }
}
fn opt_to_value(v: Option<i64>) -> Value {
match v {
Some(i) => Value::Int(i),
None => Value::Null,
}
}
pub fn alert_state_from_value(v: &Value) -> AlertState {
AlertState {
phase: v.get("phase").as_str().to_string(),
since: opt_i64(v.get("since")),
last_notified_at: opt_i64(v.get("lastNotifiedAt")),
clear_since: opt_i64(v.get("clearSince")),
}
}
pub fn alert_state_to_value(s: &AlertState) -> Value {
Value::obj(vec![
("phase", Value::str(&s.phase)),
("since", opt_to_value(s.since)),
("lastNotifiedAt", opt_to_value(s.last_notified_at)),
("clearSince", opt_to_value(s.clear_since)),
])
}
pub fn alert_policy_from_value(v: &Value) -> AlertPolicy {
AlertPolicy {
for_seconds: v.get("forSeconds").as_i64(),
clear_for_seconds: v.get("clearForSeconds").as_i64(),
renotify_seconds: opt_i64(v.get("renotifySeconds")),
}
}
pub fn alert_transition_to_value(t: &AlertTransition) -> Value {
Value::obj(vec![
("state", alert_state_to_value(&t.state)),
("notify", match &t.notify { Some(n) => Value::str(n), None => Value::Null }),
("changed", Value::Bool(t.changed)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let previous = alert_state_from_value(&args[0]);
let now = match &args[2] {
Value::Int(i) => *i,
_ => panic!("now must be a whole number of seconds"),
};
let policy = alert_policy_from_value(&args[3]);
alert_transition_to_value(&next_alert_state(&previous, args[1].as_bool(), now, &policy))
}