use super::state::HealthState;
pub const HEALTH_POLICY_VERSION: &str = "health-v1";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HealthPolicy {
pub version: String,
pub precedence: Vec<HealthState>,
pub empty_scope: HealthState,
pub missing_reference: HealthState,
}
impl Default for HealthPolicy {
fn default() -> Self {
Self::v1()
}
}
impl HealthPolicy {
pub fn v1() -> Self {
Self {
version: HEALTH_POLICY_VERSION.to_string(),
precedence: vec![
HealthState::AtRisk,
HealthState::NeedsAttention,
HealthState::OnTrack,
],
empty_scope: HealthState::Unknown,
missing_reference: HealthState::Unknown,
}
}
pub fn for_version(version: &str) -> std::result::Result<Self, HealthPolicyError> {
match version.trim() {
HEALTH_POLICY_VERSION => Ok(Self::v1()),
other => Err(HealthPolicyError {
version: other.to_string(),
}),
}
}
pub fn version(&self) -> &str {
&self.version
}
pub const KNOWN: [&'static str; 1] = [HEALTH_POLICY_VERSION];
pub fn worst(&self, states: impl IntoIterator<Item = HealthState>) -> Option<HealthState> {
let mut worst: Option<HealthState> = None;
for state in states {
if !self.precedence.contains(&state) {
continue;
}
let replace = match worst {
None => true,
Some(current) => {
let rank = |state: HealthState| {
self.precedence
.iter()
.position(|candidate| *candidate == state)
.unwrap_or(usize::MAX)
};
rank(state) < rank(current)
}
};
if replace {
worst = Some(state);
}
}
worst
}
pub const fn empty_scope(&self) -> HealthState {
self.empty_scope
}
pub const fn missing_reference(&self) -> HealthState {
self.missing_reference
}
pub fn provenance(&self) -> String {
format!(
"{} (worst of {}; empty scope is {}; a missing reference contributes {})",
self.version,
self.precedence
.iter()
.map(|state| state.as_str())
.collect::<Vec<_>>()
.join(" > "),
self.empty_scope.as_str(),
self.missing_reference.as_str()
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HealthPolicyError {
pub version: String,
}
impl HealthPolicyError {
pub fn render(&self) -> String {
format!(
"`{}` is not a health policy this build implements; known versions: {}",
self.version,
HealthPolicy::KNOWN.join(", ")
)
}
}
impl std::fmt::Display for HealthPolicyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.render())
}
}
impl std::error::Error for HealthPolicyError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn health_policy_v1_is_the_documented_formula() {
let policy = HealthPolicy::v1();
assert_eq!(policy.version(), "health-v1");
assert_eq!(
policy.precedence,
vec![
HealthState::AtRisk,
HealthState::NeedsAttention,
HealthState::OnTrack
],
"worst first"
);
assert_eq!(policy.empty_scope(), HealthState::Unknown);
assert_eq!(policy.missing_reference(), HealthState::Unknown);
let provenance = policy.provenance();
assert!(provenance.starts_with("health-v1"), "{provenance}");
assert!(
provenance.contains("at_risk > needs_attention > on_track"),
"{provenance}"
);
}
#[test]
fn health_policy_worst_picks_by_precedence() {
let policy = HealthPolicy::v1();
assert_eq!(policy.worst([]), None);
assert_eq!(
policy.worst([HealthState::OnTrack, HealthState::NeedsAttention]),
Some(HealthState::NeedsAttention)
);
assert_eq!(
policy.worst([
HealthState::AtRisk,
HealthState::NeedsAttention,
HealthState::OnTrack
]),
Some(HealthState::AtRisk)
);
assert_eq!(
policy.worst([HealthState::Unknown, HealthState::OnTrack]),
Some(HealthState::OnTrack)
);
assert_eq!(policy.worst([HealthState::Unknown]), None);
}
#[test]
fn health_policy_versions_are_a_closed_set() {
assert_eq!(
HealthPolicy::for_version("health-v1").expect("the current version"),
HealthPolicy::v1()
);
let error = HealthPolicy::for_version("health-v2").expect_err("not implemented");
assert!(error.render().contains("health-v1"), "{}", error.render());
assert!(error.render().contains("health-v2"), "{}", error.render());
assert!(HealthPolicy::for_version("").is_err());
}
}