use std::collections::HashSet;
use crate::domain::verdict::{JudgeOpinion, Outcome, RuleOutcome, RuleVerdict, Violation};
pub fn tally(name: &str, verdicts: &[RuleVerdict]) -> RuleOutcome {
let total = verdicts.len() as u32;
let votes_hold = verdicts.iter().filter(|v| v.holds).count() as u32;
let passes = votes_hold * 2 > total;
let violations = if passes {
Vec::new()
} else {
dedup(
verdicts
.iter()
.filter(|v| !v.holds)
.flat_map(|v| v.violations.iter().cloned()),
)
};
let judges = if total > 1 {
verdicts
.iter()
.map(|v| JudgeOpinion {
holds: v.holds,
rationale: clean_rationale(v),
})
.collect()
} else {
Vec::new()
};
RuleOutcome {
name: name.to_string(),
rationale: pick_rationale(verdicts, passes),
outcome: if passes { Outcome::Pass } else { Outcome::Fail },
votes_total: total,
votes_hold,
judges,
violations,
}
}
fn clean_rationale(v: &RuleVerdict) -> Option<String> {
v.rationale
.as_ref()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(str::to_string)
}
fn pick_rationale(verdicts: &[RuleVerdict], passes: bool) -> Option<String> {
verdicts
.iter()
.filter(|v| v.holds == passes)
.find_map(clean_rationale)
.or_else(|| verdicts.iter().find_map(clean_rationale))
}
fn dedup(items: impl Iterator<Item = Violation>) -> Vec<Violation> {
let mut seen = HashSet::new();
let mut out = Vec::new();
for v in items {
if seen.insert(v.dedup_key()) {
out.push(v);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn v(holds: bool, msg: &str) -> RuleVerdict {
RuleVerdict {
holds,
violations: if holds {
vec![]
} else {
vec![Violation {
message: Some(msg.into()),
..Default::default()
}]
},
rationale: None,
}
}
fn v_why(holds: bool, why: &str) -> RuleVerdict {
RuleVerdict {
rationale: Some(why.into()),
..v(holds, "bad")
}
}
#[test]
fn single_pass() {
let o = tally("r", &[v(true, "")]);
assert_eq!(o.outcome, Outcome::Pass);
assert_eq!(o.votes_total, 1);
assert_eq!(o.votes_hold, 1);
assert!(o.violations.is_empty());
}
#[test]
fn single_fail_keeps_violation() {
let o = tally("r", &[v(false, "bad")]);
assert_eq!(o.outcome, Outcome::Fail);
assert_eq!(o.violations.len(), 1);
assert_eq!(o.violations[0].message.as_deref(), Some("bad"));
}
#[test]
fn majority_pass_three_judges() {
let o = tally("r", &[v(true, ""), v(false, "x"), v(true, "")]);
assert_eq!(o.outcome, Outcome::Pass);
assert_eq!(o.votes_hold, 2);
assert!(o.violations.is_empty());
}
#[test]
fn majority_fail_three_judges_unions_violations() {
let o = tally("r", &[v(false, "a"), v(true, ""), v(false, "b")]);
assert_eq!(o.outcome, Outcome::Fail);
assert_eq!(o.votes_hold, 1);
assert_eq!(o.violations.len(), 2);
}
#[test]
fn tie_fails() {
let o = tally("r", &[v(true, ""), v(false, "x")]);
assert_eq!(o.outcome, Outcome::Fail);
}
#[test]
fn duplicate_violations_are_deduped() {
let o = tally("r", &[v(false, "same"), v(false, "same")]);
assert_eq!(o.violations.len(), 1);
}
#[test]
fn rationale_comes_from_a_judge_that_agreed_with_the_outcome() {
let o = tally(
"r",
&[
v_why(true, "complies"),
v_why(false, "looks off"),
v_why(true, "ok"),
],
);
assert_eq!(o.outcome, Outcome::Pass);
assert_eq!(o.rationale.as_deref(), Some("complies"));
let o = tally(
"r",
&[v_why(false, "inline sql at db.rs:42"), v_why(true, "fine")],
);
assert_eq!(o.outcome, Outcome::Fail);
assert_eq!(o.rationale.as_deref(), Some("inline sql at db.rs:42"));
}
#[test]
fn multi_judge_keeps_each_judges_opinion_single_judge_does_not() {
let o = tally(
"r",
&[v_why(false, "j1"), v_why(true, "j2"), v_why(false, "j3")],
);
assert_eq!(o.outcome, Outcome::Fail);
assert_eq!(o.judges.len(), 3);
assert_eq!(
o.judges.iter().map(|j| j.holds).collect::<Vec<_>>(),
[false, true, false]
);
assert_eq!(o.judges[1].rationale.as_deref(), Some("j2"));
let o = tally("r", &[v_why(false, "solo")]);
assert!(o.judges.is_empty());
assert_eq!(o.rationale.as_deref(), Some("solo"));
}
#[test]
fn rationale_falls_back_when_the_majority_gave_none_and_is_absent_when_disabled() {
let o = tally("r", &[v(true, ""), v(true, ""), v_why(false, "stray")]);
assert_eq!(o.outcome, Outcome::Pass);
assert_eq!(o.rationale.as_deref(), Some("stray"));
let o = tally("r", &[v(true, ""), v(true, "")]);
assert_eq!(o.rationale, None);
}
}