use crate::{DeltaKind, DiagnosticDelta, ReportStatus, Severity};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeltaRule {
AnyDifference,
NewAtOrAbove { minimum: Severity },
IntroducedAtOrAbove { minimum: Severity },
SeverityIncreaseAtOrAbove { minimum: Severity },
}
impl DeltaRule {
pub const fn as_str(self) -> &'static str {
match self {
Self::AnyDifference => "any_difference",
Self::NewAtOrAbove { .. } => "new_at_or_above",
Self::IntroducedAtOrAbove { .. } => "introduced_at_or_above",
Self::SeverityIncreaseAtOrAbove { .. } => "severity_increase_at_or_above",
}
}
pub const fn minimum_severity(self) -> Option<Severity> {
match self {
Self::AnyDifference => None,
Self::NewAtOrAbove { minimum }
| Self::IntroducedAtOrAbove { minimum }
| Self::SeverityIncreaseAtOrAbove { minimum } => Some(minimum),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeltaViolation {
rule: DeltaRule,
count: usize,
}
impl DeltaViolation {
const fn new(rule: DeltaRule, count: usize) -> Self {
Self { rule, count }
}
pub const fn rule(self) -> DeltaRule {
self.rule
}
pub const fn count(self) -> usize {
self.count
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DeltaEvaluation {
violations: Vec<DeltaViolation>,
}
impl DeltaEvaluation {
fn new(violations: Vec<DeltaViolation>) -> Self {
Self { violations }
}
pub fn violations(&self) -> &[DeltaViolation] {
&self.violations
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &DeltaViolation> {
self.violations.iter()
}
pub fn len(&self) -> usize {
self.violations.len()
}
pub fn is_empty(&self) -> bool {
self.violations.is_empty()
}
pub fn status(&self) -> ReportStatus {
if self.violations.is_empty() {
ReportStatus::Success
} else {
ReportStatus::Failure
}
}
pub fn is_success(&self) -> bool {
self.status().is_success()
}
pub fn is_failure(&self) -> bool {
self.status().is_failure()
}
pub fn exit_code(&self) -> u8 {
self.status().exit_code()
}
pub fn violation(&self, rule: DeltaRule) -> Option<&DeltaViolation> {
self.violations
.iter()
.find(|violation| violation.rule == rule)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeltaPolicy {
any_difference: bool,
new_at_or_above: Option<Severity>,
introduced_at_or_above: Option<Severity>,
severity_increase_at_or_above: Option<Severity>,
}
impl Default for DeltaPolicy {
fn default() -> Self {
Self::new()
}
}
impl DeltaPolicy {
pub const fn new() -> Self {
Self {
any_difference: false,
new_at_or_above: None,
introduced_at_or_above: None,
severity_increase_at_or_above: None,
}
}
pub const fn ci() -> Self {
Self::new()
.fail_on_new_at_or_above(Severity::Error)
.fail_on_severity_increase_at_or_above(Severity::Error)
}
pub const fn fail_on_any_difference(mut self) -> Self {
self.any_difference = true;
self
}
pub const fn fail_on_new_at_or_above(mut self, minimum: Severity) -> Self {
self.new_at_or_above = Some(minimum);
self
}
pub const fn fail_on_introduced_at_or_above(mut self, minimum: Severity) -> Self {
self.introduced_at_or_above = Some(minimum);
self
}
pub const fn fail_on_severity_increase_at_or_above(mut self, minimum: Severity) -> Self {
self.severity_increase_at_or_above = Some(minimum);
self
}
pub const fn fail_on_new_errors(self) -> Self {
self.fail_on_new_at_or_above(Severity::Error)
}
pub const fn fail_on_new_warnings_or_worse(self) -> Self {
self.fail_on_new_at_or_above(Severity::Warning)
}
pub const fn fail_on_error_regressions(self) -> Self {
self.fail_on_severity_increase_at_or_above(Severity::Error)
}
pub const fn fails_on_any_difference(&self) -> bool {
self.any_difference
}
pub const fn new_threshold(&self) -> Option<Severity> {
self.new_at_or_above
}
pub const fn introduced_threshold(&self) -> Option<Severity> {
self.introduced_at_or_above
}
pub const fn severity_increase_threshold(&self) -> Option<Severity> {
self.severity_increase_at_or_above
}
pub const fn is_permissive(&self) -> bool {
!self.any_difference
&& self.new_at_or_above.is_none()
&& self.introduced_at_or_above.is_none()
&& self.severity_increase_at_or_above.is_none()
}
pub fn evaluate(&self, delta: &DiagnosticDelta) -> DeltaEvaluation {
let mut violations = Vec::new();
if self.any_difference {
push_violation(
&mut violations,
DeltaRule::AnyDifference,
delta.counts().differences(),
);
}
if let Some(minimum) = self.new_at_or_above {
push_violation(
&mut violations,
DeltaRule::NewAtOrAbove { minimum },
count_new_at_or_above(delta, minimum),
);
}
if let Some(minimum) = self.introduced_at_or_above {
push_violation(
&mut violations,
DeltaRule::IntroducedAtOrAbove { minimum },
delta.introduced_at_or_above(minimum),
);
}
if let Some(minimum) = self.severity_increase_at_or_above {
push_violation(
&mut violations,
DeltaRule::SeverityIncreaseAtOrAbove { minimum },
delta.severity_increases_at_or_above(minimum),
);
}
DeltaEvaluation::new(violations)
}
}
impl DiagnosticDelta {
pub fn evaluate(&self, policy: &DeltaPolicy) -> DeltaEvaluation {
policy.evaluate(self)
}
}
fn push_violation(violations: &mut Vec<DeltaViolation>, rule: DeltaRule, count: usize) {
if count != 0 {
violations.push(DeltaViolation::new(rule, count));
}
}
fn count_new_at_or_above(delta: &DiagnosticDelta, minimum: Severity) -> usize {
delta
.iter_kind(DeltaKind::New)
.filter(|entry| {
entry
.candidate()
.is_some_and(|diagnostic| diagnostic.severity >= minimum)
})
.count()
}