covguard_output_features/
lib.rs1use serde::{Deserialize, Serialize};
7
8use covguard_render::{DEFAULT_MAX_ANNOTATIONS, DEFAULT_MAX_LINES, DEFAULT_MAX_SARIF_RESULTS};
9use covguard_types::Truncation;
10
11#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
13pub struct OutputFeatureConfig {
14 #[serde(default)]
16 pub max_markdown_lines: Option<usize>,
17 #[serde(default)]
19 pub max_annotations: Option<usize>,
20 #[serde(default)]
22 pub max_sarif_results: Option<usize>,
23}
24
25impl OutputFeatureConfig {
26 pub fn materialize(self, base: OutputFeatureFlags) -> OutputFeatureFlags {
28 OutputFeatureFlags {
29 max_markdown_lines: self.max_markdown_lines.unwrap_or(base.max_markdown_lines),
30 max_annotations: self.max_annotations.unwrap_or(base.max_annotations),
31 max_sarif_results: self.max_sarif_results.unwrap_or(base.max_sarif_results),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct OutputFeatureFlags {
39 pub max_markdown_lines: usize,
41 pub max_annotations: usize,
43 pub max_sarif_results: usize,
45}
46
47impl Default for OutputFeatureFlags {
48 fn default() -> Self {
49 Self {
50 max_markdown_lines: DEFAULT_MAX_LINES,
51 max_annotations: DEFAULT_MAX_ANNOTATIONS,
52 max_sarif_results: DEFAULT_MAX_SARIF_RESULTS,
53 }
54 }
55}
56
57pub const DEFAULT_ANNOTATION_LIMIT: usize = DEFAULT_MAX_ANNOTATIONS;
59pub const DEFAULT_MARKDOWN_LINES: usize = DEFAULT_MAX_LINES;
60pub const DEFAULT_SARIF_RESULTS: usize = DEFAULT_MAX_SARIF_RESULTS;
61
62pub fn truncate_findings<T>(findings: Vec<T>, max: Option<usize>) -> (Vec<T>, Option<Truncation>) {
64 if let Some(max) = max {
65 let total = findings.len();
66 if total > max {
67 let truncated = findings.into_iter().take(max).collect();
68 let trunc = Truncation {
69 findings_truncated: true,
70 shown: max as u32,
71 total: total as u32,
72 };
73 (truncated, Some(trunc))
74 } else {
75 (findings, None)
76 }
77 } else {
78 (findings, None)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use covguard_types::Finding;
86
87 #[test]
88 fn test_default_feature_flags() {
89 let flags = OutputFeatureFlags::default();
90 assert_eq!(flags.max_markdown_lines, DEFAULT_MARKDOWN_LINES);
91 assert_eq!(flags.max_annotations, DEFAULT_ANNOTATION_LIMIT);
92 assert_eq!(flags.max_sarif_results, DEFAULT_SARIF_RESULTS);
93 }
94
95 #[test]
96 fn test_output_feature_config_materializes_defaults() {
97 let base = OutputFeatureFlags::default();
98 let config = OutputFeatureConfig {
99 max_markdown_lines: Some(3),
100 max_annotations: None,
101 max_sarif_results: Some(5),
102 };
103 let materialized = config.materialize(base);
104
105 assert_eq!(materialized.max_markdown_lines, 3);
106 assert_eq!(materialized.max_annotations, DEFAULT_ANNOTATION_LIMIT);
107 assert_eq!(materialized.max_sarif_results, 5);
108 }
109
110 #[test]
111 fn test_output_feature_config_full_passthrough() {
112 let base = OutputFeatureFlags::default();
113 let config = OutputFeatureConfig {
114 max_markdown_lines: Some(11),
115 max_annotations: Some(22),
116 max_sarif_results: Some(33),
117 };
118 let materialized = config.materialize(base);
119
120 assert_eq!(materialized.max_markdown_lines, 11);
121 assert_eq!(materialized.max_annotations, 22);
122 assert_eq!(materialized.max_sarif_results, 33);
123 }
124
125 #[test]
126 fn test_truncate_findings_caps_results() {
127 let findings = vec![
128 Finding::uncovered_line("src/lib.rs", 1, 0),
129 Finding::uncovered_line("src/lib.rs", 2, 0),
130 ];
131 let (truncated, trunc) = truncate_findings(findings.clone(), Some(1));
132
133 assert_eq!(truncated.len(), 1);
134 assert!(trunc.is_some());
135 let trunc = trunc.expect("truncation metadata");
136 assert!(trunc.findings_truncated);
137 assert_eq!(trunc.shown, 1);
138 assert_eq!(trunc.total, findings.len() as u32);
139 }
140
141 #[test]
142 fn test_truncate_findings_passthrough_when_under_limit() {
143 let findings = vec![
144 Finding::uncovered_line("src/lib.rs", 1, 0),
145 Finding::uncovered_line("src/lib.rs", 2, 0),
146 ];
147 let (truncated, trunc) = truncate_findings(findings.clone(), Some(5));
148
149 assert_eq!(truncated.len(), findings.len());
150 assert!(trunc.is_none());
151 }
152}