cargo_crap/report/
json.rs1use crate::delta::{DeltaEntry, DeltaReport};
8use crate::merge::CrapEntry;
9use anyhow::Result;
10use std::io::Write;
11
12pub const SCHEMA_VERSION: &str = env!("CARGO_PKG_VERSION");
15
16macro_rules! schema_url {
22 ($file:literal) => {
23 concat!(
24 "https://raw.githubusercontent.com/minikin/cargo-crap/main/schemas/",
25 $file
26 )
27 };
28}
29
30pub const REPORT_SCHEMA_URL: &str = schema_url!("report-v1.json");
32
33pub const DELTA_SCHEMA_URL: &str = schema_url!("delta-v2.json");
39
40#[derive(serde::Serialize, serde::Deserialize)]
42pub struct Envelope {
43 #[serde(rename = "$schema", default, skip_serializing_if = "Option::is_none")]
46 pub schema: Option<String>,
47 pub version: String,
48 pub entries: Vec<CrapEntry>,
49}
50
51pub(crate) fn render_json(
52 entries: &[CrapEntry],
53 out: &mut dyn Write,
54) -> Result<()> {
55 let envelope = Envelope {
56 schema: Some(REPORT_SCHEMA_URL.to_string()),
57 version: SCHEMA_VERSION.to_string(),
58 entries: entries.to_vec(),
59 };
60 serde_json::to_writer_pretty(&mut *out, &envelope)?;
61 out.write_all(b"\n")?;
62 Ok(())
63}
64
65pub(crate) fn render_delta_json(
66 report: &DeltaReport,
67 out: &mut dyn Write,
68) -> Result<()> {
69 #[derive(serde::Serialize)]
70 struct DeltaOutput<'a> {
71 #[serde(rename = "$schema")]
72 schema: &'static str,
73 version: &'static str,
74 entries: &'a [DeltaEntry],
75 removed: &'a [crate::delta::RemovedEntry],
76 }
77 serde_json::to_writer_pretty(
78 &mut *out,
79 &DeltaOutput {
80 schema: DELTA_SCHEMA_URL,
81 version: SCHEMA_VERSION,
82 entries: &report.entries,
83 removed: &report.removed,
84 },
85 )?;
86 out.write_all(b"\n")?;
87 Ok(())
88}
89
90#[cfg(test)]
91mod tests {
92 use super::super::test_support::sample;
93 use super::super::{Format, render};
94 use super::*;
95 use std::path::PathBuf;
96
97 #[test]
98 fn json_output_is_envelope_with_version_and_entries() {
99 let mut buf = Vec::new();
100 render(&sample(), 30.0, Format::Json, None, &mut buf).unwrap();
101 let parsed: serde_json::Value = serde_json::from_slice(&buf).unwrap();
102 assert!(parsed.is_object(), "JSON output must be an envelope object");
103 assert_eq!(
104 parsed["version"].as_str(),
105 Some(SCHEMA_VERSION),
106 "version field must equal SCHEMA_VERSION"
107 );
108 assert!(
109 parsed["entries"].is_array(),
110 "entries field must be an array"
111 );
112 assert_eq!(
113 parsed["entries"].as_array().map(std::vec::Vec::len),
114 Some(2)
115 );
116 }
117
118 #[test]
119 fn json_format_unaffected_by_links() {
120 use super::super::SourceLinks;
121 let entries = vec![CrapEntry {
122 file: PathBuf::from("src/a.rs"),
123 function: "foo".into(),
124 line: 1,
125 cyclomatic: 1.0,
126 coverage: Some(100.0),
127 crap: 1.0,
128 crate_name: None,
129 }];
130 let links = SourceLinks::new("https://github.com/o/r".into(), "sha".into());
131 let mut buf = Vec::new();
132 render(&entries, 30.0, Format::Json, Some(&links), &mut buf).unwrap();
133 let s = String::from_utf8(buf).unwrap();
134 assert!(
135 !s.contains("](https://"),
136 "JSON output must not contain markdown links:\n{s}"
137 );
138 }
139}