sbom_diff/renderer/
json.rs1use super::{RenderOptions, Renderer, SummaryRenderer};
2use crate::{Diff, EcosystemCounts, GroupedDiff};
3use serde::Serialize;
4use std::collections::BTreeMap;
5use std::io::Write;
6
7pub struct JsonRenderer;
14
15#[derive(Serialize)]
17struct JsonOutput<'a> {
18 #[serde(flatten)]
19 diff: &'a Diff,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 ecosystem_breakdown: Option<BTreeMap<String, EcosystemCounts>>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 by_ecosystem: Option<&'a GroupedDiff>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 warnings: Option<JsonWarnings<'a>>,
26}
27
28#[derive(Serialize)]
29struct JsonWarnings<'a> {
30 old: &'a Vec<String>,
31 new: &'a Vec<String>,
32}
33
34impl Renderer for JsonRenderer {
35 fn render<W: Write>(
36 &self,
37 diff: &Diff,
38 opts: &RenderOptions,
39 writer: &mut W,
40 ) -> anyhow::Result<()> {
41 let warnings = if opts.has_warnings() {
42 Some(JsonWarnings {
43 old: &opts.old_warnings,
44 new: &opts.new_warnings,
45 })
46 } else {
47 None
48 };
49
50 if opts.group_by_ecosystem {
51 let grouped = diff.group_by_ecosystem();
52 let output = JsonOutput {
53 diff,
54 ecosystem_breakdown: Some(grouped.ecosystem_breakdown()),
55 by_ecosystem: Some(&grouped),
56 warnings,
57 };
58 serde_json::to_writer_pretty(writer, &output)?;
59 } else {
60 let output = JsonOutput {
61 diff,
62 ecosystem_breakdown: None,
63 by_ecosystem: None,
64 warnings,
65 };
66 serde_json::to_writer_pretty(writer, &output)?;
67 }
68 Ok(())
69 }
70}
71
72impl SummaryRenderer for JsonRenderer {
73 fn render_summary<W: Write>(
74 &self,
75 diff: &Diff,
76 opts: &RenderOptions,
77 writer: &mut W,
78 ) -> anyhow::Result<()> {
79 let mut summary = serde_json::json!({
80 "old_total": diff.old_total,
81 "new_total": diff.new_total,
82 "unchanged": diff.unchanged,
83 "added": diff.added.len(),
84 "removed": diff.removed.len(),
85 "changed": diff.changed.len(),
86 "edge_changes": diff.edge_diffs.len(),
87 "metadata_changed": diff.metadata_changed.is_some(),
88 });
89
90 if let Some(mc) = &diff.metadata_changed {
91 summary["metadata_changes"] = serde_json::to_value(mc)?;
92 }
93
94 if opts.has_warnings() {
95 summary["warnings"] = serde_json::json!({
96 "old": opts.old_warnings,
97 "new": opts.new_warnings,
98 });
99 }
100
101 if opts.group_by_ecosystem {
102 let breakdown = diff.ecosystem_breakdown();
103 if !breakdown.is_empty() {
104 summary["ecosystem_breakdown"] = serde_json::to_value(&breakdown)?;
105 }
106 }
107
108 serde_json::to_writer_pretty(writer, &summary)
109 .map_err(|e| anyhow::anyhow!("json summary: {}", e))
110 }
111}