Skip to main content

covguard_output/
lib.rs

1//! Output rendering utilities and feature flags for covguard reports.
2//!
3//! This crate centralizes report rendering defaults and renderer budget flags.
4//! Feature contracts are delegated to `covguard-output-features`.
5
6use covguard_render::{
7    render_annotations as render_annotations_impl, render_markdown as render_markdown_impl,
8    render_sarif as render_sarif_impl,
9};
10use covguard_types::Report;
11
12pub use covguard_output_features::{
13    DEFAULT_ANNOTATION_LIMIT, DEFAULT_MARKDOWN_LINES, DEFAULT_SARIF_RESULTS, OutputFeatureConfig,
14    OutputFeatureFlags, truncate_findings,
15};
16
17/// Backward-compatible markdown renderer with project-wide default limit.
18pub fn render_markdown(report: &Report) -> String {
19    render_markdown_with_limit(report, DEFAULT_MARKDOWN_LINES)
20}
21
22/// Markdown renderer with configurable budget.
23pub fn render_markdown_with_limit(report: &Report, max_lines: usize) -> String {
24    render_markdown_impl(report, max_lines)
25}
26
27/// Backward-compatible annotation renderer with project-wide default limit.
28pub fn render_annotations(report: &Report) -> String {
29    render_annotations_with_limit(report, DEFAULT_ANNOTATION_LIMIT)
30}
31
32/// Annotation renderer with configurable budget.
33pub fn render_annotations_with_limit(report: &Report, max_annotations: usize) -> String {
34    render_annotations_impl(report, max_annotations)
35}
36
37/// Backward-compatible SARIF renderer with project-wide default limit.
38pub fn render_sarif(report: &Report) -> String {
39    render_sarif_with_limit(report, DEFAULT_SARIF_RESULTS)
40}
41
42/// SARIF renderer with configurable budget.
43pub fn render_sarif_with_limit(report: &Report, max_results: usize) -> String {
44    render_sarif_impl(report, max_results)
45}
46
47/// Render all output formats using an explicit flag set.
48pub fn render_all(report: &Report, flags: &OutputFeatureFlags) -> (String, String, String) {
49    (
50        render_markdown_with_limit(report, flags.max_markdown_lines),
51        render_annotations_with_limit(report, flags.max_annotations),
52        render_sarif_with_limit(report, flags.max_sarif_results),
53    )
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use covguard_types::Finding;
60
61    #[test]
62    fn test_default_feature_flags() {
63        let flags = OutputFeatureFlags::default();
64        assert_eq!(flags.max_markdown_lines, DEFAULT_MARKDOWN_LINES);
65        assert_eq!(flags.max_annotations, DEFAULT_ANNOTATION_LIMIT);
66        assert_eq!(flags.max_sarif_results, DEFAULT_SARIF_RESULTS);
67    }
68
69    #[test]
70    fn test_output_feature_config_materializes_defaults() {
71        let base = OutputFeatureFlags::default();
72        let config = OutputFeatureConfig {
73            max_markdown_lines: Some(3),
74            max_annotations: None,
75            max_sarif_results: Some(5),
76        };
77        let materialized = config.materialize(base);
78
79        assert_eq!(materialized.max_markdown_lines, 3);
80        assert_eq!(materialized.max_annotations, DEFAULT_ANNOTATION_LIMIT);
81        assert_eq!(materialized.max_sarif_results, 5);
82    }
83
84    #[test]
85    fn test_output_feature_config_full_passthrough() {
86        let base = OutputFeatureFlags::default();
87        let config = OutputFeatureConfig {
88            max_markdown_lines: Some(11),
89            max_annotations: Some(22),
90            max_sarif_results: Some(33),
91        };
92        let materialized = config.materialize(base);
93
94        assert_eq!(materialized.max_markdown_lines, 11);
95        assert_eq!(materialized.max_annotations, 22);
96        assert_eq!(materialized.max_sarif_results, 33);
97    }
98
99    #[test]
100    fn test_truncate_findings_caps_results() {
101        let findings = vec![
102            Finding::uncovered_line("src/lib.rs", 1, 0),
103            Finding::uncovered_line("src/lib.rs", 2, 0),
104        ];
105        let (truncated, trunc) = truncate_findings(findings.clone(), Some(1));
106
107        assert_eq!(truncated.len(), 1);
108        assert!(trunc.is_some());
109        let trunc = trunc.expect("truncation metadata");
110        assert!(trunc.findings_truncated);
111        assert_eq!(trunc.shown, 1);
112        assert_eq!(trunc.total, findings.len() as u32);
113    }
114
115    #[test]
116    fn test_truncate_findings_passthrough_when_under_limit() {
117        let findings = vec![
118            Finding::uncovered_line("src/lib.rs", 1, 0),
119            Finding::uncovered_line("src/lib.rs", 2, 0),
120        ];
121        let (truncated, trunc) = truncate_findings(findings.clone(), Some(5));
122
123        assert_eq!(truncated.len(), findings.len());
124        assert!(trunc.is_none());
125    }
126}