use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MetricsSummary {
pub schema_version: String,
pub generated_at: String,
pub rustquty_version: String,
pub project: ProjectInfo,
pub collectors: CollectorsSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ProjectInfo {
pub name: String,
pub rust_edition: String,
pub workspace_root: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CollectorsSummary {
pub fmt: FmtResult,
pub clippy: ClippyResult,
pub tests: TestResult,
pub coverage: CoverageResult,
pub deny: DenyResult,
pub audit: AuditResult,
pub hack: HackResult,
pub mutants: MutantsResult,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FmtResult {
pub status: CollectorStatus,
#[serde(default)]
pub details: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClippyResult {
pub status: CollectorStatus,
pub warning_count: u32,
#[serde(default)]
pub details: Vec<ClippyLint>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClippyLint {
pub code: String,
pub message: String,
pub file: Option<String>,
pub line: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TestResult {
pub status: CollectorStatus,
pub passed: u32,
pub failed: u32,
pub ignored: u32,
#[serde(default)]
pub runner: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CoverageResult {
pub status: CollectorStatus,
pub line_percent: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DenyResult {
pub status: CollectorStatus,
pub banned_count: u32,
pub license_violations: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AuditResult {
pub status: CollectorStatus,
pub vulnerability_count: u32,
pub critical_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HackResult {
pub status: CollectorStatus,
pub feature_combinations_tested: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MutantsResult {
pub status: CollectorStatus,
pub mutation_score: f64,
pub caught: u32,
pub missed: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum CollectorStatus {
Pass,
Fail,
Skipped,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Baseline {
pub schema_version: String,
pub created_at: String,
pub rustquty_version: String,
pub thresholds: Thresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Thresholds {
pub fmt: FmtThreshold,
pub clippy: ClippyThreshold,
pub tests: TestThreshold,
pub coverage: CoverageThreshold,
pub deny: DenyThreshold,
pub audit: AuditThreshold,
pub hack: HackThreshold,
pub mutants: MutantsThreshold,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FmtThreshold {
pub must_pass: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClippyThreshold {
pub max_warnings: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TestThreshold {
pub max_failures: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CoverageThreshold {
pub min_line_percent: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DenyThreshold {
pub max_banned: u32,
pub max_license_violations: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AuditThreshold {
pub max_vulnerabilities: u32,
pub max_critical: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HackThreshold {
pub must_pass: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MutantsThreshold {
pub min_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct QualityReport {
pub schema_version: String,
pub generated_at: String,
pub gate_result: GateResult,
#[serde(default)]
pub violations: Vec<Violation>,
pub summary: ReportSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum GateResult {
Pass,
Fail,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Violation {
pub collector: String,
pub metric: String,
pub baseline_value: serde_json::Value,
pub current_value: serde_json::Value,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ReportSummary {
pub collectors_run: u32,
pub collectors_passed: u32,
pub collectors_failed: u32,
pub collectors_skipped: u32,
}
#[derive(Debug, thiserror::Error)]
pub enum SchemaVersionError {
#[error("unknown schema version: {0}")]
UnknownVersion(String),
}
impl MetricsSummary {
pub fn check_version(&self) -> Result<(), SchemaVersionError> {
if &self.schema_version != "1" {
return Err(SchemaVersionError::UnknownVersion(
self.schema_version.clone(),
));
}
Ok(())
}
}
impl Baseline {
pub fn check_version(&self) -> Result<(), SchemaVersionError> {
if &self.schema_version != "1" {
return Err(SchemaVersionError::UnknownVersion(
self.schema_version.clone(),
));
}
Ok(())
}
}
impl QualityReport {
pub fn check_version(&self) -> Result<(), SchemaVersionError> {
if &self.schema_version != "1" {
return Err(SchemaVersionError::UnknownVersion(
self.schema_version.clone(),
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_summary_roundtrip() {
let json = r#"{
"schemaVersion": "1",
"generatedAt": "2026-05-04T12:00:00Z",
"rustqutyVersion": "0.1.0",
"project": {
"name": "test-project",
"rustEdition": "2021",
"workspaceRoot": "/path/to/project"
},
"collectors": {
"fmt": { "status": "pass", "details": {} },
"clippy": { "status": "pass", "warningCount": 0, "details": [] },
"tests": { "status": "pass", "passed": 10, "failed": 0, "ignored": 0 },
"coverage": { "status": "skipped", "linePercent": 0.0 },
"deny": { "status": "skipped", "bannedCount": 0, "licenseViolations": 0 },
"audit": { "status": "skipped", "vulnerabilityCount": 0, "criticalCount": 0 },
"hack": { "status": "skipped", "featureCombinationsTested": 0 },
"mutants": { "status": "skipped", "mutationScore": 0.0, "caught": 0, "missed": 0 }
}
}"#;
let summary: MetricsSummary = serde_json::from_str(json).unwrap();
assert_eq!(summary.schema_version, "1");
let output = serde_json::to_string(&summary).unwrap();
assert!(output.contains("\"schemaVersion\":\"1\""));
}
#[test]
fn test_baseline_roundtrip() {
let json = r#"{
"schemaVersion": "1",
"createdAt": "2026-05-04T00:00:00Z",
"rustqutyVersion": "0.1.0",
"thresholds": {
"fmt": { "mustPass": true },
"clippy": { "maxWarnings": 0 },
"tests": { "maxFailures": 0 },
"coverage": { "minLinePercent": 80.0 },
"deny": { "maxBanned": 0, "maxLicenseViolations": 0 },
"audit": { "maxVulnerabilities": 0, "maxCritical": 0 },
"hack": { "mustPass": true },
"mutants": { "minScore": 0.8 }
}
}"#;
let baseline: Baseline = serde_json::from_str(json).unwrap();
assert_eq!(baseline.thresholds.coverage.min_line_percent, 80.0);
let output = serde_json::to_string(&baseline).unwrap();
assert!(output.contains("\"minLinePercent\":80.0"));
}
#[test]
fn test_quality_report_roundtrip() {
let json = r#"{
"schemaVersion": "1",
"generatedAt": "2026-05-04T12:00:00Z",
"gateResult": "fail",
"violations": [
{
"collector": "clippy",
"metric": "warningCount",
"baselineValue": "0",
"currentValue": "5",
"message": "clippy warning count exceeded baseline"
}
],
"summary": {
"collectorsRun": 8,
"collectorsPassed": 7,
"collectorsFailed": 1,
"collectorsSkipped": 0
}
}"#;
let report: QualityReport = serde_json::from_str(json).unwrap();
assert_eq!(report.violations.len(), 1);
let output = serde_json::to_string(&report).unwrap();
assert!(output.contains("\"gateResult\":\"fail\""));
}
#[test]
fn test_unknown_schema_version_error() {
let json = r#"{
"schemaVersion": "99",
"generatedAt": "2026-05-04T12:00:00Z",
"rustqutyVersion": "0.1.0",
"project": {
"name": "test",
"rustEdition": "2021",
"workspaceRoot": "/path"
},
"collectors": {
"fmt": { "status": "pass", "details": {} },
"clippy": { "status": "pass", "warningCount": 0, "details": [] },
"tests": { "status": "pass", "passed": 0, "failed": 0, "ignored": 0 },
"coverage": { "status": "pass", "linePercent": 0.0 },
"deny": { "status": "pass", "bannedCount": 0, "licenseViolations": 0 },
"audit": { "status": "pass", "vulnerabilityCount": 0, "criticalCount": 0 },
"hack": { "status": "pass", "featureCombinationsTested": 0 },
"mutants": { "status": "pass", "mutationScore": 0.0, "caught": 0, "missed": 0 }
}
}"#;
let summary: MetricsSummary = serde_json::from_str(json).unwrap();
let err = summary.check_version().unwrap_err();
assert!(matches!(err, SchemaVersionError::UnknownVersion(v) if v == "99"));
}
}