1use crate::semantic::{Change, ChangeKind, DiffResult};
4use serde_json::Value;
5
6pub fn format_text(result: &DiffResult, resource_name: &str) -> String {
8 if result.is_equal {
9 return format!("{}: no changes\n", resource_name);
10 }
11
12 let mut output = String::new();
13 output.push_str(&format!(
14 "{}: {} change(s)\n",
15 resource_name,
16 result.changes.len()
17 ));
18
19 for change in &result.changes {
20 output.push_str(&format_change_text(change));
21 }
22
23 output
24}
25
26fn format_change_text(change: &Change) -> String {
27 match change.kind {
28 ChangeKind::Added => {
29 let value_str = format_value_preview(change.new_value.as_ref());
30 format!(" + {}: {}\n", change.path, value_str)
31 }
32 ChangeKind::Removed => {
33 let value_str = format_value_preview(change.old_value.as_ref());
34 format!(" - {}: {}\n", change.path, value_str)
35 }
36 ChangeKind::Modified => {
37 let old_str = format_value_preview(change.old_value.as_ref());
38 let new_str = format_value_preview(change.new_value.as_ref());
39 format!(" ~ {}: {} -> {}\n", change.path, old_str, new_str)
40 }
41 }
42}
43
44fn format_value_preview(value: Option<&Value>) -> String {
45 match value {
46 None => "(none)".to_string(),
47 Some(Value::Null) => "null".to_string(),
48 Some(Value::Bool(b)) => b.to_string(),
49 Some(Value::Number(n)) => n.to_string(),
50 Some(Value::String(s)) => {
51 if s.len() > 50 {
52 format!("\"{}...\"", &s[..47])
53 } else {
54 format!("\"{}\"", s)
55 }
56 }
57 Some(Value::Array(arr)) => format!("[{} items]", arr.len()),
58 Some(Value::Object(obj)) => format!("{{...}} ({} keys)", obj.len()),
59 }
60}
61
62pub fn format_json(result: &DiffResult) -> String {
64 serde_json::to_string_pretty(result).unwrap_or_else(|_| "{}".to_string())
65}
66
67pub fn format_report(diffs: &[(String, DiffResult)], format: OutputFormat) -> String {
69 match format {
70 OutputFormat::Text => format_report_text(diffs),
71 OutputFormat::Json => format_report_json(diffs),
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum OutputFormat {
78 Text,
79 Json,
80}
81
82fn format_report_text(diffs: &[(String, DiffResult)]) -> String {
83 let mut output = String::new();
84
85 let (changed, unchanged): (Vec<_>, Vec<_>) = diffs.iter().partition(|(_, r)| !r.is_equal);
86
87 if changed.is_empty() {
88 output.push_str("No changes detected.\n");
89 return output;
90 }
91
92 output.push_str(&format!(
93 "Found {} resource(s) with changes:\n\n",
94 changed.len()
95 ));
96
97 for (name, result) in &changed {
98 output.push_str(&format_text(result, name));
99 output.push('\n');
100 }
101
102 if !unchanged.is_empty() {
103 output.push_str(&format!("{} resource(s) unchanged.\n", unchanged.len()));
104 }
105
106 output
107}
108
109fn format_report_json(diffs: &[(String, DiffResult)]) -> String {
110 let report: Vec<_> = diffs
111 .iter()
112 .map(|(name, result)| {
113 serde_json::json!({
114 "resource": name,
115 "changed": !result.is_equal,
116 "changes": result.changes
117 })
118 })
119 .collect();
120
121 serde_json::to_string_pretty(&report).unwrap_or_else(|_| "[]".to_string())
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::semantic::diff;
128 use serde_json::json;
129
130 #[test]
131 fn test_format_text_no_changes() {
132 let result = DiffResult {
133 is_equal: true,
134 changes: vec![],
135 };
136
137 let output = format_text(&result, "test-index");
138 assert!(output.contains("no changes"));
139 }
140
141 #[test]
142 fn test_format_text_with_changes() {
143 let old = json!({"name": "test", "value": 1});
144 let new = json!({"name": "test", "value": 2});
145
146 let result = diff(&old, &new, "name");
147 let output = format_text(&result, "test-index");
148
149 assert!(output.contains("1 change"));
150 assert!(output.contains("value"));
151 assert!(output.contains("~")); }
153
154 #[test]
155 fn test_format_json() {
156 let result = DiffResult {
157 is_equal: false,
158 changes: vec![Change {
159 path: "name".to_string(),
160 kind: ChangeKind::Modified,
161 old_value: Some(json!("old")),
162 new_value: Some(json!("new")),
163 }],
164 };
165
166 let output = format_json(&result);
167 assert!(output.contains("modified"));
168 assert!(output.contains("name"));
169 }
170}