use serde::{Deserialize, Serialize};
use crate::vcs::TriageDecision;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum TrailerState {
Present {
value: String,
},
PresentButEmpty,
Absent,
}
impl TrailerState {
#[must_use]
pub fn evaluate(trailers: &[(String, String)], trailer_name: &str) -> Self {
for (name, value) in trailers {
if name.eq_ignore_ascii_case(trailer_name) {
return if value.trim().is_empty() {
Self::PresentButEmpty
} else {
Self::Present {
value: value.clone(),
}
};
}
}
Self::Absent
}
#[must_use]
pub const fn is_pass(&self) -> bool {
matches!(self, Self::Present { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum RollbackTriageState {
ChainPresent {
decision: TriageDecision,
},
ChainMalformed {
value: String,
},
ChainAbsent,
}
impl RollbackTriageState {
#[must_use]
pub fn evaluate(trailers: &[(String, String)]) -> Self {
match TrailerState::evaluate(trailers, "Triage-Decision") {
TrailerState::Present { value } => TriageDecision::parse_decision(&value)
.map_or(Self::ChainMalformed { value }, |decision| {
Self::ChainPresent { decision }
}),
TrailerState::PresentButEmpty | TrailerState::Absent => Self::ChainAbsent,
}
}
#[must_use]
pub const fn is_pass(&self) -> bool {
matches!(self, Self::ChainPresent { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum BranchAttestState {
Attested {
by_role: String,
},
AttestedWithoutRole,
SidecarMissing,
}
impl BranchAttestState {
#[must_use]
pub const fn is_pass(&self) -> bool {
matches!(self, Self::Attested { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum ServerEnforcementState {
Active,
DeclaredButNotActive,
CheckFailed {
reason: String,
},
}
impl ServerEnforcementState {
#[must_use]
pub const fn is_pass(&self) -> bool {
matches!(self, Self::Active)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn trailers(pairs: &[(&str, &str)]) -> Vec<(String, String)> {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect()
}
#[test]
fn trailer_present_with_value() {
let t = trailers(&[("Triage-Decision", "abc1234")]);
let s = TrailerState::evaluate(&t, "Triage-Decision");
assert!(s.is_pass());
assert_eq!(
s,
TrailerState::Present {
value: "abc1234".into()
}
);
}
#[test]
fn trailer_case_insensitive_name_match() {
let t = trailers(&[("triage-decision", "x")]);
assert!(TrailerState::evaluate(&t, "Triage-Decision").is_pass());
}
#[test]
fn trailer_empty_value_is_not_pass() {
let t = trailers(&[("Triage-Decision", " ")]);
assert_eq!(
TrailerState::evaluate(&t, "Triage-Decision"),
TrailerState::PresentButEmpty
);
assert!(!TrailerState::evaluate(&t, "Triage-Decision").is_pass());
}
#[test]
fn trailer_absent() {
let t = trailers(&[("Some-Other", "x")]);
assert_eq!(
TrailerState::evaluate(&t, "Triage-Decision"),
TrailerState::Absent
);
}
#[test]
fn rollback_triage_chain_present_resolves_decision() {
let t = trailers(&[("Triage-Decision", "red")]);
let s = RollbackTriageState::evaluate(&t);
assert!(s.is_pass());
assert_eq!(
s,
RollbackTriageState::ChainPresent {
decision: TriageDecision::Red
}
);
}
#[test]
fn rollback_triage_chain_malformed_value() {
let t = trailers(&[("Triage-Decision", "purple")]);
assert_eq!(
RollbackTriageState::evaluate(&t),
RollbackTriageState::ChainMalformed {
value: "purple".into()
}
);
}
#[test]
fn rollback_triage_chain_absent_when_no_trailer() {
let t = trailers(&[("Reviewed-By", "navigator")]);
assert_eq!(
RollbackTriageState::evaluate(&t),
RollbackTriageState::ChainAbsent
);
assert!(!RollbackTriageState::evaluate(&t).is_pass());
}
#[test]
fn branch_attest_pass_states() {
assert!(BranchAttestState::Attested {
by_role: "navigator".into()
}
.is_pass());
assert!(!BranchAttestState::AttestedWithoutRole.is_pass());
assert!(!BranchAttestState::SidecarMissing.is_pass());
}
#[test]
fn server_enforcement_only_active_passes() {
assert!(ServerEnforcementState::Active.is_pass());
assert!(!ServerEnforcementState::DeclaredButNotActive.is_pass());
assert!(!ServerEnforcementState::CheckFailed {
reason: "network".into()
}
.is_pass());
}
#[test]
fn witness_states_serialize_kebab_tagged() {
let s = serde_json::to_string(&RollbackTriageState::ChainAbsent).unwrap();
assert_eq!(s, r#"{"kind":"chain-absent"}"#);
let s2 = serde_json::to_string(&ServerEnforcementState::DeclaredButNotActive).unwrap();
assert_eq!(s2, r#"{"kind":"declared-but-not-active"}"#);
}
}