fallow_output/
health_trends.rs1use crate::CoverageModel;
4
5#[derive(Debug, Clone, serde::Serialize)]
8#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
9pub struct HealthTrend {
10 pub compared_to: TrendPoint,
12 pub metrics: Vec<TrendMetric>,
14 pub snapshots_loaded: usize,
16 pub overall_direction: TrendDirection,
18}
19
20#[derive(Debug, Clone, serde::Serialize)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23pub struct TrendPoint {
24 pub timestamp: String,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub git_sha: Option<String>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub score: Option<f64>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub grade: Option<String>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub coverage_model: Option<CoverageModel>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub snapshot_schema_version: Option<u32>,
41}
42
43#[derive(Debug, Clone, serde::Serialize)]
45#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
46pub struct TrendMetric {
47 pub name: &'static str,
49 pub label: &'static str,
51 pub previous: f64,
53 pub current: f64,
55 pub delta: f64,
57 pub direction: TrendDirection,
59 pub unit: &'static str,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub previous_count: Option<TrendCount>,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub current_count: Option<TrendCount>,
67}
68
69#[derive(Debug, Clone, serde::Serialize)]
71#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
72pub struct TrendCount {
73 pub value: usize,
75 pub total: usize,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
82#[serde(rename_all = "snake_case")]
83pub enum TrendDirection {
84 Improving,
86 Declining,
88 Stable,
90}
91
92impl TrendDirection {
93 #[must_use]
95 pub const fn arrow(self) -> &'static str {
96 match self {
97 Self::Improving => "\u{2191}",
98 Self::Declining => "\u{2193}",
99 Self::Stable => "\u{2192}",
100 }
101 }
102
103 #[must_use]
105 pub const fn label(self) -> &'static str {
106 match self {
107 Self::Improving => "improving",
108 Self::Declining => "declining",
109 Self::Stable => "stable",
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn trend_direction_labels_are_stable() {
120 assert_eq!(TrendDirection::Improving.label(), "improving");
121 assert_eq!(TrendDirection::Declining.label(), "declining");
122 assert_eq!(TrendDirection::Stable.label(), "stable");
123 }
124
125 #[test]
126 fn trend_direction_serializes_as_snake_case() {
127 let value = serde_json::to_value(TrendDirection::Improving).expect("serialize trend");
128 assert_eq!(value, serde_json::json!("improving"));
129 }
130}