use crate::contract_drift::types::{
BreakingChangeRule, BreakingChangeRuleConfig, BreakingChangeRuleType,
};
use mockforge_foundation::contract_diff_types::Mismatch;
#[derive(Debug, Clone)]
pub struct BreakingChangeDetector {
rules: Vec<BreakingChangeRule>,
}
impl BreakingChangeDetector {
pub fn new(rules: Vec<BreakingChangeRule>) -> Self {
Self { rules }
}
pub fn is_breaking(&self, mismatch: &Mismatch) -> bool {
self.rules.iter().filter(|rule| rule.enabled).any(|rule| rule.matches(mismatch))
}
pub fn classify(&self, mismatches: &[Mismatch]) -> (Vec<Mismatch>, Vec<Mismatch>) {
let mut breaking = Vec::new();
let mut non_breaking = Vec::new();
for mismatch in mismatches {
if self.is_breaking(mismatch) {
breaking.push(mismatch.clone());
} else {
non_breaking.push(mismatch.clone());
}
}
(breaking, non_breaking)
}
pub fn classify_three_way(
&self,
mismatches: &[Mismatch],
) -> (Vec<Mismatch>, Vec<Mismatch>, Vec<Mismatch>) {
let mut non_breaking = Vec::new();
let mut potentially_breaking = Vec::new();
let mut definitely_breaking = Vec::new();
for mismatch in mismatches {
if self.is_breaking(mismatch) {
definitely_breaking.push(mismatch.clone());
}
else if mismatch.severity == mockforge_foundation::contract_diff_types::MismatchSeverity::Medium
|| matches!(
mismatch.mismatch_type,
mockforge_foundation::contract_diff_types::MismatchType::FormatMismatch
| mockforge_foundation::contract_diff_types::MismatchType::ConstraintViolation
| mockforge_foundation::contract_diff_types::MismatchType::TypeMismatch
)
{
potentially_breaking.push(mismatch.clone());
}
else {
non_breaking.push(mismatch.clone());
}
}
(non_breaking, potentially_breaking, definitely_breaking)
}
pub fn rules(&self) -> &[BreakingChangeRule] {
&self.rules
}
pub fn add_rule(&mut self, rule: BreakingChangeRule) {
self.rules.push(rule);
}
pub fn remove_rule(&mut self, index: usize) {
if index < self.rules.len() {
self.rules.remove(index);
}
}
}
impl Default for BreakingChangeDetector {
fn default() -> Self {
Self::new(vec![
BreakingChangeRule {
rule_type: BreakingChangeRuleType::Severity,
config: BreakingChangeRuleConfig::Severity {
severity: mockforge_foundation::contract_diff_types::MismatchSeverity::High,
include_higher: true,
},
enabled: true,
},
BreakingChangeRule {
rule_type: BreakingChangeRuleType::MismatchType,
config: BreakingChangeRuleConfig::MismatchType {
mismatch_type: mockforge_foundation::contract_diff_types::MismatchType::MissingRequiredField,
},
enabled: true,
},
])
}
}
#[cfg(test)]
mod tests {
use super::*;
use mockforge_foundation::contract_diff_types::{MismatchSeverity, MismatchType};
fn create_test_mismatch(mismatch_type: MismatchType, severity: MismatchSeverity) -> Mismatch {
Mismatch {
mismatch_type,
path: "body.field".to_string(),
method: Some("POST".to_string()),
expected: Some("string".to_string()),
actual: None,
description: "Test mismatch".to_string(),
severity,
confidence: 1.0,
context: std::collections::HashMap::new(),
}
}
#[test]
fn test_severity_based_detection() {
let detector = BreakingChangeDetector::default();
let critical_mismatch =
create_test_mismatch(MismatchType::TypeMismatch, MismatchSeverity::Critical);
assert!(detector.is_breaking(&critical_mismatch));
let high_mismatch =
create_test_mismatch(MismatchType::TypeMismatch, MismatchSeverity::High);
assert!(detector.is_breaking(&high_mismatch));
let medium_mismatch =
create_test_mismatch(MismatchType::TypeMismatch, MismatchSeverity::Medium);
assert!(!detector.is_breaking(&medium_mismatch));
}
#[test]
fn test_mismatch_type_based_detection() {
let detector = BreakingChangeDetector::default();
let missing_field =
create_test_mismatch(MismatchType::MissingRequiredField, MismatchSeverity::Medium);
assert!(detector.is_breaking(&missing_field));
let unexpected_field =
create_test_mismatch(MismatchType::UnexpectedField, MismatchSeverity::Medium);
assert!(!detector.is_breaking(&unexpected_field));
}
#[test]
fn test_classify() {
let detector = BreakingChangeDetector::default();
let mismatches = vec![
create_test_mismatch(MismatchType::MissingRequiredField, MismatchSeverity::Critical),
create_test_mismatch(MismatchType::UnexpectedField, MismatchSeverity::Low),
create_test_mismatch(MismatchType::TypeMismatch, MismatchSeverity::High),
];
let (breaking, non_breaking) = detector.classify(&mismatches);
assert_eq!(breaking.len(), 2); assert_eq!(non_breaking.len(), 1); }
}