use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ExpectVerdict {
Met,
NotMet,
Impaired,
}
impl ExpectVerdict {
pub fn to_judgement(self) -> crate::report::Judgement {
use crate::report::Judgement;
match self {
ExpectVerdict::NotMet => Judgement::Established,
ExpectVerdict::Met => Judgement::NotEstablished {
reason: "the expectation held within the window".into(),
},
ExpectVerdict::Impaired => Judgement::Unobservable {
reason: "the observation cannot carry the claim (RFC 09 §5.1 O6)".into(),
},
}
}
}
impl From<crate::report::Judgement> for ExpectVerdict {
fn from(j: crate::report::Judgement) -> ExpectVerdict {
use crate::report::Judgement;
match j {
Judgement::Established => ExpectVerdict::NotMet,
Judgement::NotEstablished { .. } => ExpectVerdict::Met,
Judgement::NotAsked | Judgement::Unobservable { .. } => ExpectVerdict::Impaired,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ExpectReport {
pub selector: String,
pub window_s: f64,
pub ended_early: bool,
pub samples: u64,
pub keys_seen: usize,
pub dropped: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_hz: Option<f64>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub violations: Vec<String>,
pub violations_total: u64,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub unmet: Vec<String>,
pub verdict: ExpectVerdict,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expect_report_json_shape_is_pinned() {
let report = ExpectReport {
selector: "v1/*/state/sysinfo/health".into(),
window_s: 2.5,
ended_early: true,
samples: 3,
keys_seen: 2,
dropped: 0,
rate_hz: None,
violations: vec![],
violations_total: 0,
unmet: vec![],
verdict: ExpectVerdict::Met,
};
let json = serde_json::to_value(&report).unwrap();
assert_eq!(
json,
serde_json::json!({
"selector": "v1/*/state/sysinfo/health",
"window_s": 2.5,
"ended_early": true,
"samples": 3,
"keys_seen": 2,
"dropped": 0,
"violations_total": 0,
"verdict": "met",
})
);
let report = ExpectReport {
rate_hz: Some(0.5),
violations: vec!["k: invalid — /x: 3 is not a string".into()],
violations_total: 1,
unmet: vec!["1 sample(s) violated a per-sample requirement".into()],
verdict: ExpectVerdict::NotMet,
ended_early: false,
..report
};
let json = serde_json::to_value(&report).unwrap();
assert_eq!(json["rate_hz"], 0.5);
assert_eq!(json["verdict"], "not_met");
assert_eq!(json["violations"][0], "k: invalid — /x: 3 is not a string");
}
}