Skip to main content

miwa/
output.rs

1//! Text / JSON rendering of findings.
2
3fn kind_label(kind: crate::check::FindingKind) -> &'static str {
4    use crate::check::FindingKind::*;
5    match kind {
6        Drift => "drift",
7        New => "new",
8        Removed => "removed",
9        Broken => "broken",
10        Orphan => "orphan",
11        NoTest => "no-test",
12        Invalid => "invalid",
13        Index => "index",
14    }
15}
16
17/// One line per finding. `"no findings\n"` if empty.
18pub fn render_text(findings: &[crate::check::Finding]) -> String {
19    if findings.is_empty() {
20        return "no findings\n".to_string();
21    }
22    let mut out = String::new();
23    for f in findings {
24        out.push_str(&format!(
25            "{} {:?} {} {:?} {}\n",
26            kind_label(f.kind),
27            f.target_kind,
28            f.target,
29            f.reason,
30            f.detail
31        ));
32    }
33    out
34}
35
36/// `{"findings":[...]}`, pretty-printed.
37pub fn render_json(findings: &[crate::check::Finding]) -> String {
38    #[derive(serde::Serialize)]
39    struct Wrapper<'a> {
40        findings: &'a [crate::check::Finding],
41    }
42    serde_json::to_string_pretty(&Wrapper { findings }).expect("findings serialize to json")
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use crate::check::{Finding, FindingKind};
49    use crate::model::{ReasonCode, TargetKind};
50
51    fn sample_findings() -> Vec<Finding> {
52        vec![
53            Finding {
54                kind: FindingKind::Drift,
55                target_kind: TargetKind::Edge,
56                target: "doc-req:DOC-A:FR-001".to_string(),
57                reason: ReasonCode::DocChanged,
58                detail: "doc changed".to_string(),
59            },
60            Finding {
61                kind: FindingKind::NoTest,
62                target_kind: TargetKind::Req,
63                target: "FR-002".to_string(),
64                reason: ReasonCode::ReqWithoutTest,
65                detail: "no test found".to_string(),
66            },
67        ]
68    }
69
70    // [FR-032]
71    #[test]
72    fn render_textはfindingなしなら固定文言になる() {
73        assert_eq!(render_text(&[]), "no findings\n");
74    }
75
76    // [FR-032]
77    #[test]
78    fn render_textは1行1findingでkindとtargetを含む() {
79        let findings = sample_findings();
80        let text = render_text(&findings);
81        let lines: Vec<&str> = text.lines().collect();
82        assert_eq!(lines.len(), findings.len());
83        assert!(lines[0].contains("doc-req:DOC-A:FR-001"));
84        assert!(lines[0].to_lowercase().contains("drift"));
85        assert!(lines[1].contains("FR-002"));
86        assert!(lines[1].to_lowercase().contains("no")); // no-test / NoTest 系の文字列を含む
87    }
88
89    // [FR-032]
90    #[test]
91    fn render_jsonはfindings配列にkind_target_kind_target_reason_detailを持つ() {
92        let findings = sample_findings();
93        let json = render_json(&findings);
94        let value: serde_json::Value = serde_json::from_str(&json).expect("valid json");
95        let arr = value
96            .get("findings")
97            .and_then(|v| v.as_array())
98            .expect("findings array");
99        assert_eq!(arr.len(), 2);
100        for (i, item) in arr.iter().enumerate() {
101            assert!(item.get("kind").is_some(), "missing kind at {i}");
102            assert!(
103                item.get("target_kind").is_some(),
104                "missing target_kind at {i}"
105            );
106            assert!(item.get("target").is_some(), "missing target at {i}");
107            assert!(item.get("reason").is_some(), "missing reason at {i}");
108            assert!(item.get("detail").is_some(), "missing detail at {i}");
109        }
110        assert_eq!(arr[0]["kind"], serde_json::json!("Drift"));
111        assert_eq!(arr[0]["target"], serde_json::json!("doc-req:DOC-A:FR-001"));
112        assert_eq!(arr[0]["reason"], serde_json::json!("DocChanged"));
113        assert_eq!(arr[1]["kind"], serde_json::json!("NoTest"));
114    }
115}