Skip to main content

moonlight_core/
lib.rs

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}