Skip to main content

gts_validator/
output.rs

1//! Shared output formatting for validation reports.
2//!
3//! Provides JSON and plain-text formatters for `ValidationReport`.
4//! Color/terminal formatting is intentionally excluded from this core module —
5//! that concern belongs to the CLI layer.
6
7use std::io::Write;
8
9use crate::report::ValidationReport;
10
11/// Format a `ValidationReport` as JSON to a writer.
12///
13/// # Errors
14///
15/// Returns an error if serialization or writing fails.
16pub fn write_json(report: &ValidationReport, writer: &mut dyn Write) -> anyhow::Result<()> {
17    let json = serde_json::to_string_pretty(report)?;
18    writeln!(writer, "{json}")?;
19    Ok(())
20}
21
22/// Format a `ValidationReport` as human-readable plain text to a writer.
23///
24/// Color/ANSI formatting is the responsibility of the caller (CLI layer).
25///
26/// # Errors
27///
28/// Returns an error if writing fails.
29pub fn write_human(report: &ValidationReport, writer: &mut dyn Write) -> anyhow::Result<()> {
30    writeln!(writer)?;
31    writeln!(writer, "{}", "=".repeat(80))?;
32    writeln!(writer, "  GTS DOCUMENTATION VALIDATOR")?;
33    writeln!(writer, "{}", "=".repeat(80))?;
34    writeln!(writer)?;
35    writeln!(writer, "  Files scanned:  {}", report.scanned_files)?;
36    writeln!(writer, "  Files failed:   {}", report.failed_files)?;
37    writeln!(writer, "  Errors found:   {}", report.errors_count())?;
38    writeln!(writer)?;
39
40    if !report.scan_errors.is_empty() {
41        writeln!(writer, "{}", "-".repeat(80))?;
42        writeln!(writer, "  SCAN ERRORS (files that could not be validated)")?;
43        writeln!(writer, "{}", "-".repeat(80))?;
44        for scan_err in &report.scan_errors {
45            writeln!(writer, "{}", scan_err.format_human_readable())?;
46        }
47        writeln!(writer)?;
48    }
49
50    if !report.validation_errors.is_empty() {
51        writeln!(writer, "{}", "-".repeat(80))?;
52        writeln!(writer, "  VALIDATION ERRORS")?;
53        writeln!(writer, "{}", "-".repeat(80))?;
54        for error in &report.validation_errors {
55            writeln!(writer, "{}", error.format_human_readable())?;
56        }
57        writeln!(writer)?;
58    }
59
60    writeln!(writer, "{}", "=".repeat(80))?;
61    if report.ok {
62        writeln!(
63            writer,
64            "\u{2713} All {} files passed validation",
65            report.scanned_files
66        )?;
67    } else {
68        if !report.scan_errors.is_empty() {
69            writeln!(
70                writer,
71                "\u{2717} {} file(s) could not be scanned \u{2014} CI must treat this as a failure",
72                report.failed_files
73            )?;
74        }
75        if !report.validation_errors.is_empty() {
76            writeln!(
77                writer,
78                "\u{2717} {} invalid GTS identifier(s) found",
79                report.errors_count()
80            )?;
81            writeln!(writer)?;
82            writeln!(writer, "  To fix:")?;
83
84            let has_vendor_mismatch = report
85                .validation_errors
86                .iter()
87                .any(|e| e.error.contains("Vendor mismatch"));
88            let has_wildcard_error = report
89                .validation_errors
90                .iter()
91                .any(|e| e.error.contains("Wildcard"));
92            let has_parse_error = report
93                .validation_errors
94                .iter()
95                .any(|e| !e.error.contains("Vendor mismatch") && !e.error.contains("Wildcard"));
96
97            if has_parse_error {
98                writeln!(
99                    writer,
100                    "    - Schema IDs must end with ~ (e.g., gts.x.core.type.v1~)"
101                )?;
102                writeln!(
103                    writer,
104                    "    - Each segment needs 5 parts: vendor.package.namespace.type.version"
105                )?;
106                writeln!(writer, "    - No hyphens allowed, use underscores")?;
107            }
108            if has_wildcard_error {
109                writeln!(
110                    writer,
111                    "    - Wildcards (*) only in filter/pattern contexts"
112                )?;
113            }
114            if has_vendor_mismatch {
115                writeln!(writer, "    - Ensure all GTS IDs use the expected vendor")?;
116            }
117        }
118    }
119    writeln!(writer, "{}", "=".repeat(80))?;
120
121    Ok(())
122}