crates/gunnar-ui-web/src/admit.rs
//! **The Admission panel's words** — `GET /api/namespaces/{name}/admit`, the
//! plan gunnar's admission gate would make for this namespace's next transfer.
//!
//! Host-compiled for [`crate::activity`]'s reason: `pages::usage` is wasm-only,
//! so every sentence the panel decides lives here where `cargo test` runs it.
//! The per-kind sentence is gunnar's own and is shown verbatim; the only line
//! composed here is the budget line.
use serde::Deserialize;
use crate::fmt;
/// One kind of work's plan, as the BFF sends it.
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct AdmitKind {
pub kind: String,
pub class: String,
pub why: String,
/// `None` = gunnar has no estimate yet (never drawn as "0 B").
pub estimate_bytes: Option<u64>,
pub samples: u64,
}
/// The plan, as the BFF sends it.
#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct AdmitPlan {
pub namespace: String,
pub policy: String,
pub window_days: u32,
pub used_bytes: u64,
pub limit_bytes: Option<u64>,
pub budget_left_bytes: Option<u64>,
pub kinds: Vec<AdmitKind>,
}
/// **The budget line.** "No budget" is said as such — a box without the usage
/// share has none, and a `0` would read as "nothing left".
pub fn budget_line(plan: &AdmitPlan) -> String {
let used = format!("{} moved in the last {} days", fmt::bytes(plan.used_bytes), plan.window_days);
match (plan.limit_bytes, plan.budget_left_bytes) {
(Some(limit), Some(0)) => format!(
"{used} — over the {} budget: transfers are served at the reduced share until the window moves",
fmt::bytes(limit)
),
(Some(limit), Some(left)) => {
format!("{used} of a {} budget; {} left at the full share", fmt::bytes(limit), fmt::bytes(left))
}
_ => format!("{used}; this box sets no transfer budget"),
}
}
/// A kind's estimate, or the words for having none.
pub fn estimate_line(kind: &AdmitKind) -> String {
match kind.estimate_bytes {
Some(bytes) => format!("~{} per {} ({} measured)", fmt::bytes(bytes), kind.kind, kind.samples),
None => format!("no estimate for a {} yet", kind.kind),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn plan(limit: Option<u64>, left: Option<u64>) -> AdmitPlan {
AdmitPlan {
namespace: "alpha".into(),
policy: "usage-share".into(),
window_days: 30,
used_bytes: 2048,
limit_bytes: limit,
budget_left_bytes: left,
kinds: vec![],
}
}
#[test]
fn no_budget_is_said_and_not_drawn_as_zero_left() {
let line = budget_line(&plan(None, None));
assert!(line.contains("sets no transfer budget"), "{line}");
assert!(!line.contains(" 0 "), "{line}");
}
#[test]
fn over_budget_says_what_happens() {
assert!(budget_line(&plan(Some(1000), Some(0))).contains("reduced share"));
assert!(budget_line(&plan(Some(1 << 20), Some(1 << 19))).contains("left at the full share"));
}
#[test]
fn an_unlearnt_estimate_is_words_not_zero() {
let k = AdmitKind { kind: "push".into(), class: "Full".into(), why: String::new(), estimate_bytes: None, samples: 0 };
assert_eq!(estimate_line(&k), "no estimate for a push yet");
}
}