Skip to main content

agent_rules_tool/
report.rs

1//! YAML serialization of lint reports for CLI `--report` output.
2
3use crate::error::Error;
4use crate::spec::SPEC_COMMIT;
5use crate::{FileLintResult, LintReport, Severity, Violation};
6use serde::Serialize;
7
8/// One violation in a YAML lint report.
9#[derive(Debug, Serialize)]
10pub struct ReportViolation {
11    /// `"warn"` or `"error"`.
12    pub severity: String,
13    /// Frontmatter field or JSON pointer path.
14    pub field: String,
15    /// Human-readable message.
16    pub message: String,
17    /// Spec or schema URL citation.
18    pub spec_ref: String,
19}
20
21/// Per-file section of an aggregate YAML report.
22#[derive(Debug, Serialize)]
23pub struct FileReport {
24    /// File path relative to the lint root.
25    pub path: String,
26    /// Whether this file had no violations.
27    pub valid: bool,
28    /// Violations found in this file.
29    pub violations: Vec<ReportViolation>,
30}
31
32/// Directory lint report written by `--report` in directory mode.
33#[derive(Debug, Serialize)]
34pub struct AggregateReport {
35    /// Pinned spec commit at time of lint ([`SPEC_COMMIT`]).
36    pub spec_commit: String,
37    /// `true` when every file is valid.
38    pub valid: bool,
39    /// Per-file results.
40    pub files: Vec<FileReport>,
41}
42
43/// Single-file lint report written by `--report` in file mode.
44#[derive(Debug, Serialize)]
45pub struct SingleFileReport {
46    /// Pinned spec commit at time of lint ([`SPEC_COMMIT`]).
47    pub spec_commit: String,
48    /// Whether the file had no violations.
49    pub valid: bool,
50    /// Violations found.
51    pub violations: Vec<ReportViolation>,
52}
53
54impl From<&Violation> for ReportViolation {
55    fn from(v: &Violation) -> Self {
56        Self {
57            severity: match v.severity {
58                Severity::Warn => "warn".to_string(),
59                Severity::Error => "error".to_string(),
60            },
61            field: v.field.clone(),
62            message: v.message.clone(),
63            spec_ref: v.spec_ref.to_string(),
64        }
65    }
66}
67
68/// Serialize one [`LintReport`] to YAML for `--report` output.
69pub fn report_to_yaml_single(report: &LintReport) -> Result<String, Error> {
70    let doc = SingleFileReport {
71        spec_commit: SPEC_COMMIT.to_string(),
72        valid: report.valid,
73        violations: report.violations.iter().map(Into::into).collect(),
74    };
75    serde_saphyr::to_string(&doc).map_err(|e| Error::Yaml(e.to_string()))
76}
77
78/// Serialize directory lint results to YAML for `--report` output.
79pub fn report_to_yaml_aggregate(results: &[FileLintResult]) -> Result<String, Error> {
80    let files = results
81        .iter()
82        .map(|r| FileReport {
83            path: r.path.display().to_string(),
84            valid: r.report.valid,
85            violations: r.report.violations.iter().map(Into::into).collect(),
86        })
87        .collect();
88    let doc = AggregateReport {
89        spec_commit: SPEC_COMMIT.to_string(),
90        valid: aggregate_valid(results),
91        files,
92    };
93    serde_saphyr::to_string(&doc).map_err(|e| Error::Yaml(e.to_string()))
94}
95
96fn aggregate_valid(results: &[FileLintResult]) -> bool {
97    results.iter().all(|r| r.report.valid)
98}