use crate::conjunct::ConjunctStatus;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "verdict")]
pub enum Verdict {
#[serde(rename = "attested")]
Attested,
#[serde(rename = "not_attested")]
NotAttested {
#[serde(skip_serializing_if = "Vec::is_empty")]
reasons: Vec<String>,
},
#[serde(rename = "insufficient_data")]
InsufficientData {
#[serde(skip_serializing_if = "Vec::is_empty")]
missing: Vec<String>,
},
}
impl Verdict {
pub fn attested() -> Self {
Verdict::Attested
}
pub fn not_attested(reasons: Vec<&str>) -> Self {
Verdict::NotAttested {
reasons: reasons.iter().map(|s| s.to_string()).collect(),
}
}
pub fn insufficient_data(missing: Vec<&str>) -> Self {
Verdict::InsufficientData {
missing: missing.iter().map(|s| s.to_string()).collect(),
}
}
}
pub fn verdict(conjunct_statuses: &[ConjunctStatus; 4], consistency_passed: bool) -> Verdict {
let conjuncts = [
("generality", conjunct_statuses[0]),
("economic_substitutability", conjunct_statuses[1]),
("environmental_transfer", conjunct_statuses[2]),
("autonomous_agency", conjunct_statuses[3]),
];
let fail_count = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::Fail)
.count();
if fail_count > 0 {
let failed: Vec<&str> = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::Fail)
.map(|(name, _)| *name)
.collect();
return Verdict::not_attested(failed);
}
let partial_count = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::Partial)
.count();
if partial_count > 0 {
let partialed: Vec<&str> = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::Partial)
.map(|(name, _)| *name)
.collect();
return Verdict::not_attested(partialed);
}
let pass_count = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::Pass)
.count();
let insufficient_count = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::InsufficientData)
.count();
if insufficient_count > 0 {
let insufficient: Vec<&str> = conjuncts
.iter()
.filter(|(_, s)| *s == ConjunctStatus::InsufficientData)
.map(|(name, _)| *name)
.collect();
return Verdict::insufficient_data(insufficient);
}
if pass_count == 4 {
if consistency_passed {
return Verdict::attested();
} else {
return Verdict::not_attested(vec!["consistency_check"]);
}
}
Verdict::not_attested(vec!["unknown_state"])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verdict_all_pass_consistency_pass() {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::Attested));
}
#[test]
fn verdict_all_pass_consistency_fail() {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
];
let v = verdict(&statuses, false);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
#[test]
fn verdict_any_fail() {
let statuses = [
ConjunctStatus::Fail,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
#[test]
fn verdict_any_partial() {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::Partial,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
#[test]
fn verdict_any_insufficient_data_no_fail() {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::InsufficientData,
];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::InsufficientData { .. }));
}
#[test]
fn verdict_fail_dominates_insufficient_data() {
let statuses = [
ConjunctStatus::Fail,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::InsufficientData,
];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
#[test]
fn verdict_partial_dominates_insufficient_data() {
let statuses = [
ConjunctStatus::Partial,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::InsufficientData,
];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
#[test]
fn verdict_exhaustive_512_cases() {
let statuses_list = vec![
ConjunctStatus::Pass,
ConjunctStatus::Partial,
ConjunctStatus::Fail,
ConjunctStatus::InsufficientData,
];
let mut case_count = 0;
for g in &statuses_list {
for e in &statuses_list {
for env in &statuses_list {
for a in &statuses_list {
for consistency in &[true, false] {
let statuses = [*g, *e, *env, *a];
let _v = verdict(&statuses, *consistency);
case_count += 1;
}
}
}
}
}
assert_eq!(case_count, 512);
}
#[test]
fn verdict_reasons_match_failed_conjuncts() {
let statuses = [
ConjunctStatus::Fail,
ConjunctStatus::Partial,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
];
let v = verdict(&statuses, true);
match v {
Verdict::NotAttested { reasons } => {
assert!(!reasons.is_empty());
assert!(reasons.contains(&"generality".to_string()));
}
_ => panic!("Expected NotAttested"),
}
}
#[test]
fn verdict_insufficient_data_reasons() {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::InsufficientData,
ConjunctStatus::Pass,
ConjunctStatus::InsufficientData,
];
let v = verdict(&statuses, true);
match v {
Verdict::InsufficientData { missing } => {
assert_eq!(missing.len(), 2);
assert!(missing.contains(&"economic_substitutability".to_string()));
assert!(missing.contains(&"autonomous_agency".to_string()));
}
_ => panic!("Expected InsufficientData"),
}
}
}
#[cfg(test)]
mod property_tests {
use super::*;
#[test]
fn property_verdict_deterministic_on_repeated_calls() {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::Partial,
ConjunctStatus::Fail,
ConjunctStatus::InsufficientData,
];
let v1 = verdict(&statuses, true);
let v2 = verdict(&statuses, true);
let v3 = verdict(&statuses, true);
let d1 = format!("{:?}", v1);
let d2 = format!("{:?}", v2);
let d3 = format!("{:?}", v3);
assert_eq!(d1, d2);
assert_eq!(d2, d3);
}
#[test]
fn property_verdict_fail_always_not_attested() {
let status_options = vec![
ConjunctStatus::Pass,
ConjunctStatus::Partial,
ConjunctStatus::Fail,
ConjunctStatus::InsufficientData,
];
for e in &status_options {
for env in &status_options {
for a in &status_options {
let statuses = [ConjunctStatus::Fail, *e, *env, *a];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
}
}
}
#[test]
fn property_verdict_partial_dominates_insufficient() {
let status_options = vec![
ConjunctStatus::Pass,
ConjunctStatus::Partial,
ConjunctStatus::InsufficientData,
];
for e in &status_options {
for env in &status_options {
for a in &status_options {
let statuses = [ConjunctStatus::Partial, *e, *env, *a];
let v = verdict(&statuses, true);
assert!(matches!(v, Verdict::NotAttested { .. }));
}
}
}
}
#[test]
fn property_verdict_insufficient_only_without_fail_partial() {
let status_options = vec![ConjunctStatus::Pass, ConjunctStatus::InsufficientData];
for e in &status_options {
for env in &status_options {
for a in &status_options {
for consistency in &[true, false] {
let statuses = [ConjunctStatus::Pass, *e, *env, *a];
let v = verdict(&statuses, *consistency);
let has_insufficient = [*e, *env, *a]
.iter()
.any(|s| *s == ConjunctStatus::InsufficientData);
if has_insufficient {
assert!(matches!(v, Verdict::InsufficientData { .. }));
}
}
}
}
}
}
#[test]
fn property_verdict_all_pass_requires_consistency() {
for consistency in &[true, false] {
let statuses = [
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
ConjunctStatus::Pass,
];
let v = verdict(&statuses, *consistency);
if *consistency {
assert!(matches!(v, Verdict::Attested));
} else {
assert!(matches!(v, Verdict::NotAttested { .. }));
}
}
}
#[test]
fn property_verdict_not_attested_always_has_reasons() {
let status_options = vec![
ConjunctStatus::Pass,
ConjunctStatus::Partial,
ConjunctStatus::Fail,
ConjunctStatus::InsufficientData,
];
for g in &status_options {
for e in &status_options {
for env in &status_options {
for a in &status_options {
for consistency in &[true, false] {
let statuses = [*g, *e, *env, *a];
let v = verdict(&statuses, *consistency);
if let Verdict::NotAttested { reasons } = v {
assert!(!reasons.is_empty(), "NotAttested must have reasons");
}
}
}
}
}
}
}
#[test]
fn property_verdict_insufficient_always_has_missing() {
let status_options = vec![ConjunctStatus::Pass, ConjunctStatus::InsufficientData];
for g in &status_options {
for e in &status_options {
for env in &status_options {
for a in &status_options {
for consistency in &[true, false] {
let statuses = [*g, *e, *env, *a];
let v = verdict(&statuses, *consistency);
if let Verdict::InsufficientData { missing } = v {
assert!(!missing.is_empty(), "InsufficientData must have missing");
}
}
}
}
}
}
}
}