use std::fmt;
use std::path::PathBuf;
use fallow_types::serde_path;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum RuntimeCoverageSchemaVersion {
#[default]
#[serde(rename = "1")]
V1,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum RuntimeCoverageReportVerdict {
Clean,
HotPathTouched,
ColdCodeDetected,
LicenseExpiredGrace,
#[default]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum RuntimeCoverageSignal {
LicenseExpiredGrace,
ColdCodeDetected,
HotPathTouched,
}
impl RuntimeCoverageSignal {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::LicenseExpiredGrace => "license-expired-grace",
Self::ColdCodeDetected => "cold-code-detected",
Self::HotPathTouched => "hot-path-touched",
}
}
}
impl fmt::Display for RuntimeCoverageSignal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl RuntimeCoverageReportVerdict {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Clean => "clean",
Self::HotPathTouched => "hot-path-touched",
Self::ColdCodeDetected => "cold-code-detected",
Self::LicenseExpiredGrace => "license-expired-grace",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for RuntimeCoverageReportVerdict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum RuntimeCoverageVerdict {
SafeToDelete,
ReviewRequired,
CoverageUnavailable,
LowTraffic,
Active,
Unknown,
}
impl RuntimeCoverageVerdict {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::SafeToDelete => "safe_to_delete",
Self::ReviewRequired => "review_required",
Self::CoverageUnavailable => "coverage_unavailable",
Self::LowTraffic => "low_traffic",
Self::Active => "active",
Self::Unknown => "unknown",
}
}
#[must_use]
pub const fn human_label(self) -> &'static str {
match self {
Self::SafeToDelete => "safe to delete",
Self::ReviewRequired => "review required",
Self::CoverageUnavailable => "coverage unavailable",
Self::LowTraffic => "low traffic",
Self::Active => "active",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for RuntimeCoverageVerdict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum RuntimeCoverageConfidence {
VeryHigh,
High,
Medium,
Low,
None,
Unknown,
}
impl RuntimeCoverageConfidence {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::VeryHigh => "very_high",
Self::High => "high",
Self::Medium => "medium",
Self::Low => "low",
Self::None => "none",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for RuntimeCoverageConfidence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum RuntimeCoverageWatermark {
TrialExpired,
LicenseExpiredGrace,
Unknown,
}
impl RuntimeCoverageWatermark {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::TrialExpired => "trial-expired",
Self::LicenseExpiredGrace => "license-expired-grace",
Self::Unknown => "unknown",
}
}
}
impl fmt::Display for RuntimeCoverageWatermark {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum RuntimeCoverageDataSource {
#[default]
Local,
Cloud,
}
impl RuntimeCoverageDataSource {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Local => "local",
Self::Cloud => "cloud",
}
}
}
impl fmt::Display for RuntimeCoverageDataSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Default, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageSummary {
pub data_source: RuntimeCoverageDataSource,
pub last_received_at: Option<String>,
pub functions_tracked: usize,
pub functions_hit: usize,
pub functions_unhit: usize,
pub functions_untracked: usize,
pub coverage_percent: f64,
pub trace_count: u64,
pub period_days: u32,
pub deployments_seen: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capture_quality: Option<RuntimeCoverageCaptureQuality>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageCaptureQuality {
pub window_seconds: u64,
pub instances_observed: u32,
pub lazy_parse_warning: bool,
pub untracked_ratio_percent: f64,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageEvidence {
pub static_status: String,
pub test_coverage: String,
pub v8_tracking: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub untracked_reason: Option<String>,
pub observation_days: u32,
pub deployments_observed: u32,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageAction {
#[serde(rename = "type")]
pub kind: String,
pub description: String,
pub auto_fixable: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageMessage {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageDiscriminators {
pub tracking_state: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub invocation_ratio: Option<f64>,
pub low_traffic_threshold: f64,
pub trace_count: u64,
pub min_observation_volume: u32,
pub meets_observation_volume: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageFinding {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub stable_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub source_hash: Option<String>,
#[serde(serialize_with = "serde_path::serialize")]
pub path: PathBuf,
pub function: String,
pub line: u32,
pub verdict: RuntimeCoverageVerdict,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub invocations: Option<u64>,
pub confidence: RuntimeCoverageConfidence,
pub evidence: RuntimeCoverageEvidence,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "schema", schemars(default))]
pub actions: Vec<RuntimeCoverageAction>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub discriminators: Option<RuntimeCoverageDiscriminators>,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageHotPath {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub stable_id: Option<String>,
#[serde(serialize_with = "serde_path::serialize")]
pub path: PathBuf,
pub function: String,
pub line: u32,
pub end_line: u32,
pub invocations: u64,
pub percentile: u8,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "schema", schemars(default))]
pub actions: Vec<RuntimeCoverageAction>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum RuntimeCoverageRiskBand {
Low,
Medium,
High,
}
impl RuntimeCoverageRiskBand {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
}
}
}
impl fmt::Display for RuntimeCoverageRiskBand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageBlastRadiusEntry {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub stable_id: Option<String>,
#[serde(serialize_with = "serde_path::serialize")]
pub file: PathBuf,
pub function: String,
pub line: u32,
pub caller_count: u32,
pub caller_count_weighted_by_traffic: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deploys_touched: Option<u32>,
pub risk_band: RuntimeCoverageRiskBand,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageImportanceEntry {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub stable_id: Option<String>,
#[serde(serialize_with = "serde_path::serialize")]
pub file: PathBuf,
pub function: String,
pub line: u32,
pub invocations: u64,
pub cyclomatic: u32,
pub owner_count: u32,
pub importance_score: f64,
pub reason: String,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageReport {
pub schema_version: RuntimeCoverageSchemaVersion,
pub verdict: RuntimeCoverageReportVerdict,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "schema", schemars(default))]
pub signals: Vec<RuntimeCoverageSignal>,
pub summary: RuntimeCoverageSummary,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "schema", schemars(default))]
pub findings: Vec<RuntimeCoverageFinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "schema", schemars(default))]
pub hot_paths: Vec<RuntimeCoverageHotPath>,
pub blast_radius: Vec<RuntimeCoverageBlastRadiusEntry>,
pub importance: Vec<RuntimeCoverageImportanceEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub watermark: Option<RuntimeCoverageWatermark>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[cfg_attr(feature = "schema", schemars(default))]
pub warnings: Vec<RuntimeCoverageMessage>,
pub actionable: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub actionability_reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub actionability_verdict: Option<String>,
pub provenance: RuntimeCoverageProvenance,
}
#[derive(Debug, Clone, serde::Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RuntimeCoverageProvenance {
pub data_source: RuntimeCoverageDataSource,
pub is_production: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "schema", schemars(default))]
pub freshness_days: Option<u32>,
pub untracked_ratio: f64,
pub unresolved_ratio: f64,
pub stale: bool,
pub stale_after_days: u32,
}
impl Default for RuntimeCoverageProvenance {
fn default() -> Self {
Self {
data_source: RuntimeCoverageDataSource::Local,
is_production: "unknown".to_owned(),
freshness_days: Some(0),
untracked_ratio: 0.0,
unresolved_ratio: 0.0,
stale: false,
stale_after_days: RUNTIME_STALE_AFTER_DAYS,
}
}
}
pub const RUNTIME_STALE_AFTER_DAYS: u32 = 14;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn report_verdict_display_matches_kebab_case_serde() {
assert_eq!(RuntimeCoverageReportVerdict::Clean.to_string(), "clean");
assert_eq!(
RuntimeCoverageReportVerdict::HotPathTouched.to_string(),
"hot-path-touched",
);
assert_eq!(
RuntimeCoverageReportVerdict::ColdCodeDetected.to_string(),
"cold-code-detected",
);
assert_eq!(
RuntimeCoverageReportVerdict::LicenseExpiredGrace.to_string(),
"license-expired-grace",
);
assert_eq!(RuntimeCoverageReportVerdict::Unknown.to_string(), "unknown",);
}
#[test]
fn verdict_display_matches_snake_case_serde() {
assert_eq!(
RuntimeCoverageVerdict::SafeToDelete.to_string(),
"safe_to_delete",
);
assert_eq!(
RuntimeCoverageVerdict::ReviewRequired.to_string(),
"review_required",
);
assert_eq!(
RuntimeCoverageVerdict::CoverageUnavailable.to_string(),
"coverage_unavailable",
);
assert_eq!(
RuntimeCoverageVerdict::LowTraffic.to_string(),
"low_traffic",
);
assert_eq!(RuntimeCoverageVerdict::Active.to_string(), "active");
}
#[test]
fn confidence_display_matches_snake_case_serde() {
assert_eq!(RuntimeCoverageConfidence::VeryHigh.to_string(), "very_high",);
assert_eq!(RuntimeCoverageConfidence::High.to_string(), "high");
assert_eq!(RuntimeCoverageConfidence::Medium.to_string(), "medium");
assert_eq!(RuntimeCoverageConfidence::Low.to_string(), "low");
assert_eq!(RuntimeCoverageConfidence::None.to_string(), "none");
assert_eq!(RuntimeCoverageConfidence::Unknown.to_string(), "unknown");
}
#[test]
fn watermark_display_matches_kebab_case_serde() {
assert_eq!(
RuntimeCoverageWatermark::TrialExpired.to_string(),
"trial-expired",
);
assert_eq!(
RuntimeCoverageWatermark::LicenseExpiredGrace.to_string(),
"license-expired-grace",
);
}
#[test]
fn action_serializes_kind_as_type() {
let action = RuntimeCoverageAction {
kind: "review-deletion".to_owned(),
description: "Remove the function.".to_owned(),
auto_fixable: false,
};
let value = serde_json::to_value(&action).expect("action should serialize");
assert_eq!(value["type"], "review-deletion");
assert!(
value.get("kind").is_none(),
"kind should be renamed to type"
);
}
}