use std::fmt;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Status {
CorrectlyRejected,
FalseAcceptance,
Repaired,
RejectedNotRepaired,
RepairDiverges,
RequiresReference,
ModeDivergencePinned,
FormAxisOk,
FormAxisDiverges,
FormAxisPinned,
InvariantViolationMust,
InvariantViolationShould,
Preserved,
ProjectionDiverges,
ProjectionPinned,
ProjectionUnavailablePinned,
ProjectionErrorPinned,
ProjectionSplitsSingleMember,
Unknown(String),
}
impl Status {
pub fn as_str(&self) -> &str {
match self {
Status::CorrectlyRejected => "correctly-rejected",
Status::FalseAcceptance => "false-acceptance",
Status::Repaired => "repaired",
Status::RejectedNotRepaired => "rejected-not-repaired",
Status::RepairDiverges => "repair-diverges",
Status::RequiresReference => "requires-reference",
Status::ModeDivergencePinned => "mode-divergence-pinned",
Status::FormAxisOk => "form-axis-ok",
Status::FormAxisDiverges => "form-axis-diverges",
Status::FormAxisPinned => "form-axis-pinned",
Status::InvariantViolationMust => "invariant-violation-must",
Status::InvariantViolationShould => "invariant-violation-should",
Status::Preserved => "preserved",
Status::ProjectionDiverges => "projection-diverges",
Status::ProjectionPinned => "projection-pinned",
Status::ProjectionUnavailablePinned => "projection-unavailable-pinned",
Status::ProjectionErrorPinned => "projection-error-pinned",
Status::ProjectionSplitsSingleMember => "projection-splits-single-member",
Status::Unknown(s) => s,
}
}
pub fn from_wire(s: &str) -> Self {
match s {
"correctly-rejected" => Status::CorrectlyRejected,
"false-acceptance" => Status::FalseAcceptance,
"repaired" => Status::Repaired,
"rejected-not-repaired" => Status::RejectedNotRepaired,
"repair-diverges" => Status::RepairDiverges,
"requires-reference" => Status::RequiresReference,
"mode-divergence-pinned" => Status::ModeDivergencePinned,
"form-axis-ok" => Status::FormAxisOk,
"form-axis-diverges" => Status::FormAxisDiverges,
"form-axis-pinned" => Status::FormAxisPinned,
"invariant-violation-must" => Status::InvariantViolationMust,
"invariant-violation-should" => Status::InvariantViolationShould,
"preserved" => Status::Preserved,
"projection-diverges" => Status::ProjectionDiverges,
"projection-pinned" => Status::ProjectionPinned,
"projection-unavailable-pinned" => Status::ProjectionUnavailablePinned,
"projection-error-pinned" => Status::ProjectionErrorPinned,
"projection-splits-single-member" => Status::ProjectionSplitsSingleMember,
other => Status::Unknown(other.to_string()),
}
}
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for Status {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for Status {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Status::from_wire(&s))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Expectation {
SpecMandated,
PinnedBaseline,
}
impl Expectation {
pub fn as_str(&self) -> &'static str {
match self {
Expectation::SpecMandated => "spec-mandated",
Expectation::PinnedBaseline => "pinned-baseline",
}
}
}
impl fmt::Display for Expectation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum NormativeLevel {
#[serde(rename = "must")]
Must,
#[serde(rename = "should")]
Should,
#[serde(rename = "n/a")]
Na,
}
impl NormativeLevel {
pub fn as_str(&self) -> &'static str {
match self {
NormativeLevel::Must => "must",
NormativeLevel::Should => "should",
NormativeLevel::Na => "n/a",
}
}
}
impl fmt::Display for NormativeLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_known_variants_round_trip() {
let names = [
"correctly-rejected",
"false-acceptance",
"repaired",
"rejected-not-repaired",
"repair-diverges",
"requires-reference",
"mode-divergence-pinned",
"form-axis-ok",
"form-axis-diverges",
"form-axis-pinned",
"invariant-violation-must",
"invariant-violation-should",
"preserved",
"projection-diverges",
"projection-pinned",
"projection-unavailable-pinned",
"projection-error-pinned",
"projection-splits-single-member",
];
assert_eq!(
names.len(),
18,
"a Status variant was added or removed — update `names` above so the \
new variant's round-trip is actually covered"
);
for name in names {
let status = Status::from_wire(name);
assert!(
!matches!(status, Status::Unknown(_)),
"{name} should map to a known variant"
);
assert_eq!(status.as_str(), name);
let json = serde_json::to_string(&status).unwrap();
assert_eq!(json, format!("\"{name}\""));
let back: Status = serde_json::from_str(&json).unwrap();
assert_eq!(back, status);
}
}
#[test]
fn status_unknown_preserves_name() {
let status: Status = serde_json::from_str("\"projection panicked\"").unwrap();
assert_eq!(status, Status::Unknown("projection panicked".to_string()));
assert_eq!(status.as_str(), "projection panicked");
assert_eq!(
serde_json::to_string(&status).unwrap(),
"\"projection panicked\""
);
}
#[test]
fn expectation_round_trips() {
for (variant, wire) in [
(Expectation::SpecMandated, "\"spec-mandated\""),
(Expectation::PinnedBaseline, "\"pinned-baseline\""),
] {
assert_eq!(serde_json::to_string(&variant).unwrap(), wire);
assert_eq!(serde_json::from_str::<Expectation>(wire).unwrap(), variant);
}
}
#[test]
fn normative_level_round_trips() {
for (variant, wire) in [
(NormativeLevel::Must, "\"must\""),
(NormativeLevel::Should, "\"should\""),
(NormativeLevel::Na, "\"n/a\""),
] {
assert_eq!(serde_json::to_string(&variant).unwrap(), wire);
assert_eq!(
serde_json::from_str::<NormativeLevel>(wire).unwrap(),
variant
);
}
}
#[test]
fn strict_enums_reject_unknown() {
assert!(serde_json::from_str::<Expectation>("\"made-up\"").is_err());
assert!(serde_json::from_str::<NormativeLevel>("\"maybe\"").is_err());
}
}