pub mod confidence_scorer;
pub mod correction_proposer;
pub mod diff_analyzer;
pub mod recommendation_engine;
pub mod semantic_analyzer;
pub mod types;
pub use confidence_scorer::{ConfidenceScorer, ScoringContext};
pub use correction_proposer::CorrectionProposer;
pub use diff_analyzer::DiffAnalyzer;
pub use recommendation_engine::{RecommendationEngine, RequestContext};
pub use semantic_analyzer::{SemanticAnalyzer, SemanticChangeType, SemanticDriftResult};
pub use types::ConfidenceLevel;
pub use types::{
CapturedRequest, ContractDiffConfig, ContractDiffResult, CorrectionProposal, DiffMetadata,
Mismatch, MismatchSeverity, MismatchType, PatchOperation, Recommendation,
};
pub struct ContractDiffAnalyzer {
diff_analyzer: DiffAnalyzer,
recommendation_engine: RecommendationEngine,
semantic_analyzer: SemanticAnalyzer,
#[allow(dead_code)]
correction_proposer: CorrectionProposer,
config: ContractDiffConfig,
}
impl ContractDiffAnalyzer {
pub fn new(config: ContractDiffConfig) -> crate::Result<Self> {
let diff_analyzer = DiffAnalyzer::new(config.clone());
let recommendation_engine = RecommendationEngine::new(config.clone())?;
let semantic_analyzer = SemanticAnalyzer::new(config.clone())?;
let correction_proposer = CorrectionProposer;
Ok(Self {
diff_analyzer,
recommendation_engine,
semantic_analyzer,
correction_proposer,
config,
})
}
pub async fn analyze(
&self,
request: &CapturedRequest,
spec: &crate::openapi::OpenApiSpec,
) -> crate::Result<ContractDiffResult> {
let mut result = self.diff_analyzer.analyze_request(request, spec).await?;
if self.config.use_ai_recommendations && !result.mismatches.is_empty() {
let mut request_context = RequestContext::new(&request.method, &request.path);
if let Some(body) = &request.body {
request_context = request_context.with_body(body.clone());
}
request_context =
request_context.with_contract_format(&result.metadata.contract_format);
let recommendations = self
.recommendation_engine
.generate_recommendations(&result.mismatches, &request_context)
.await?;
result.recommendations = recommendations
.into_iter()
.filter(|r| r.confidence >= self.config.confidence_threshold)
.collect();
}
if self.config.generate_corrections && !result.mismatches.is_empty() {
result.corrections = CorrectionProposer::generate_proposals(
&result.mismatches,
&result.recommendations,
spec,
);
result.corrections.retain(|c| c.confidence >= self.config.confidence_threshold);
}
result.confidence = ConfidenceScorer::calculate_overall_confidence(&result.mismatches);
Ok(result)
}
pub async fn compare_specs(
&self,
before_spec: &crate::openapi::OpenApiSpec,
after_spec: &crate::openapi::OpenApiSpec,
endpoint_path: &str,
method: &str,
) -> crate::Result<Option<SemanticDriftResult>> {
if self.config.semantic_analysis_enabled {
let semantic_result = self
.semantic_analyzer
.analyze_semantic_drift(before_spec, after_spec, endpoint_path, method)
.await?;
if let Some(ref result) = semantic_result {
if result.semantic_confidence >= self.config.semantic_confidence_threshold {
return Ok(semantic_result);
}
}
}
Ok(None)
}
pub fn generate_patch_file(
&self,
corrections: &[CorrectionProposal],
spec_version: &str,
) -> serde_json::Value {
CorrectionProposer::generate_patch_file(corrections, spec_version)
}
pub fn config(&self) -> &ContractDiffConfig {
&self.config
}
}