#![forbid(unsafe_code)]
use serde::{Deserialize, Serialize};
pub const PROTOCOL_VERSION: &str = "1.0.0";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Request {
pub protocol_version: String,
pub license: License,
pub project_root: String,
pub coverage_sources: Vec<CoverageSource>,
pub static_findings: StaticFindings,
#[serde(default)]
pub options: Options,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct License {
pub jwt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum CoverageSource {
V8 { path: String },
Istanbul { path: String },
V8Dir { path: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StaticFindings {
pub files: Vec<StaticFile>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StaticFile {
pub path: String,
pub functions: Vec<StaticFunction>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StaticFunction {
pub name: String,
pub start_line: u32,
pub end_line: u32,
pub cyclomatic: u32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Options {
#[serde(default)]
pub include_hot_paths: bool,
#[serde(default)]
pub min_invocations_for_hot: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Response {
pub protocol_version: String,
pub verdict: Verdict,
pub summary: Summary,
pub findings: Vec<Finding>,
#[serde(default)]
pub hot_paths: Vec<HotPath>,
#[serde(default)]
pub watermark: Option<Watermark>,
#[serde(default)]
pub errors: Vec<DiagnosticMessage>,
#[serde(default)]
pub warnings: Vec<DiagnosticMessage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Verdict {
Clean,
HotPathChangesNeeded,
ColdCodeDetected,
LicenseExpiredGrace,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Summary {
pub functions_total: u64,
pub functions_called: u64,
pub functions_never_called: u64,
pub functions_coverage_unavailable: u64,
pub percent_dead_in_production: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Finding {
pub file: String,
pub function: String,
pub state: CallState,
pub invocations: u64,
pub confidence: Confidence,
#[serde(default)]
pub actions: Vec<Action>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CallState {
Called,
NeverCalled,
CoverageUnavailable,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Confidence {
High,
Medium,
Low,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HotPath {
pub file: String,
pub function: String,
pub invocations: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Action {
pub kind: String,
pub description: String,
#[serde(default)]
pub auto_fixable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Watermark {
TrialExpired,
LicenseExpiredGrace,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticMessage {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Feature {
ProductionCoverage,
PortfolioDashboard,
McpCloudTools,
CrossRepoAggregation,
#[serde(other)]
Unknown,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_constant_parses_as_semver_major() {
assert!(PROTOCOL_VERSION.starts_with("1."));
}
#[test]
fn unknown_verdict_round_trips() {
let json = r#""something-new""#;
let verdict: Verdict = serde_json::from_str(json).unwrap();
matches!(verdict, Verdict::Unknown);
}
#[test]
fn unknown_feature_round_trips() {
let json = r#""future_feature""#;
let feature: Feature = serde_json::from_str(json).unwrap();
matches!(feature, Feature::Unknown);
}
#[test]
fn coverage_source_kebab_case() {
let json = r#"{"kind":"v8-dir","path":"/tmp/dumps"}"#;
let src: CoverageSource = serde_json::from_str(json).unwrap();
matches!(src, CoverageSource::V8Dir { .. });
}
#[test]
fn response_allows_unknown_fields() {
let json = r#"{
"protocol_version": "1.0.0",
"verdict": "clean",
"summary": {
"functions_total": 0,
"functions_called": 0,
"functions_never_called": 0,
"functions_coverage_unavailable": 0,
"percent_dead_in_production": 0.0
},
"findings": [],
"future_top_level_field": 42
}"#;
let response: Response = serde_json::from_str(json).unwrap();
assert_eq!(response.protocol_version, "1.0.0");
}
}