1pub mod compare;
2pub mod config;
3pub mod storage;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
11#[serde(rename_all = "snake_case")]
12pub enum Classification {
13 #[default]
14 Match,
15 SuspiciousDifference,
16 ReferenceNoise,
17 SuspiciousWithNoise,
18 TargetError,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
22#[serde(rename_all = "snake_case")]
23pub enum DiffKind {
24 Status,
25 Header,
26 Body,
27 Stderr,
28 TargetError,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
32pub struct DiffEntry {
33 pub kind: DiffKind,
34 pub path: String,
35 pub primary: Option<String>,
36 pub candidate: Option<String>,
37 pub secondary: Option<String>,
38 pub message: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, Default)]
42pub struct ComparisonSummary {
43 pub classification: Classification,
44 pub raw_candidate_diffs: Vec<DiffEntry>,
45 pub reference_noise: Vec<DiffEntry>,
46 pub noise_filtered_diffs: Vec<DiffEntry>,
47 pub raw_diff_summary: String,
48 pub noise_summary: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct BodyCapture {
53 pub size_bytes: usize,
54 pub sha256: String,
55 pub preview: String,
56 pub truncated: bool,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct TargetObservation {
61 pub status: Option<u16>,
62 pub headers: BTreeMap<String, String>,
63 pub body: BodyCapture,
64 pub stderr: Option<BodyCapture>,
65 pub latency_ms: u128,
66 pub error: Option<String>,
67}
68
69#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
70#[serde(rename_all = "snake_case")]
71pub enum Adapter {
72 Http,
73 Cli,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(untagged)]
78pub enum RunInput {
79 Http {
80 method: String,
81 path: String,
82 query: Option<String>,
83 },
84 Cli {
85 primary_command: String,
86 candidate_command: String,
87 secondary_command: Option<String>,
88 },
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ComparisonRun {
93 pub id: Uuid,
94 pub timestamp: DateTime<Utc>,
95 pub adapter: Adapter,
96 pub input: RunInput,
97 pub request_headers: BTreeMap<String, String>,
98 pub request_body: BodyCapture,
99 pub primary: TargetObservation,
100 pub candidate: TargetObservation,
101 pub secondary: Option<TargetObservation>,
102 pub comparison: ComparisonSummary,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ComparisonRunListItem {
107 pub id: Uuid,
108 pub timestamp: DateTime<Utc>,
109 pub adapter: Adapter,
110 pub input: RunInput,
111 pub primary_status: Option<u16>,
112 pub candidate_status: Option<u16>,
113 pub secondary_status: Option<u16>,
114 pub classification: Classification,
115 pub primary_latency_ms: u128,
116 pub candidate_latency_ms: u128,
117 pub secondary_latency_ms: Option<u128>,
118 pub diff_count: usize,
119 pub noise_count: usize,
120}
121
122impl From<&ComparisonRun> for ComparisonRunListItem {
123 fn from(run: &ComparisonRun) -> Self {
124 Self {
125 id: run.id,
126 timestamp: run.timestamp,
127 adapter: run.adapter,
128 input: run.input.clone(),
129 primary_status: run.primary.status,
130 candidate_status: run.candidate.status,
131 secondary_status: run.secondary.as_ref().and_then(|target| target.status),
132 classification: run.comparison.classification.clone(),
133 primary_latency_ms: run.primary.latency_ms,
134 candidate_latency_ms: run.candidate.latency_ms,
135 secondary_latency_ms: run.secondary.as_ref().map(|target| target.latency_ms),
136 diff_count: run.comparison.noise_filtered_diffs.len(),
137 noise_count: run.comparison.reference_noise.len(),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct LatencyStats {
144 pub primary_avg_ms: f64,
145 pub candidate_avg_ms: f64,
146 pub secondary_avg_ms: Option<f64>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct StatsSummary {
151 pub total_runs: usize,
152 pub matches: usize,
153 pub suspicious_differences: usize,
154 pub reference_noise: usize,
155 pub suspicious_with_noise: usize,
156 pub target_errors: usize,
157 pub latency: LatencyStats,
158 pub latest_runs: Vec<ComparisonRunListItem>,
159}