braze_sync/format/
table.rs1use crate::diff::catalog::CatalogSchemaDiff;
8use crate::diff::content_block::ContentBlockDiff;
9use crate::diff::custom_attribute::{CustomAttributeDiff, CustomAttributeOp};
10use crate::diff::email_template::EmailTemplateDiff;
11use crate::diff::{DiffOp, DiffSummary, ResourceDiff};
12use crate::resource::{CatalogField, ResourceKind};
13use std::fmt::Write as _;
14
15pub fn render(summary: &DiffSummary) -> String {
16 let mut out = String::new();
17
18 for diff in &summary.diffs {
19 render_one(&mut out, diff);
20 out.push('\n');
21 }
22
23 let _ = writeln!(
24 out,
25 "Summary: {} changed, {} in sync, {} orphan, {} destructive",
26 summary.changed_count(),
27 summary.in_sync_count(),
28 summary.orphan_count(),
29 summary.destructive_count(),
30 );
31
32 out
33}
34
35fn render_one(out: &mut String, diff: &ResourceDiff) {
36 let unchanged = !diff.has_changes();
37 let icon = if unchanged {
38 "โ
"
39 } else {
40 kind_icon(diff.kind())
41 };
42 let label = kind_label(diff.kind());
43 let _ = writeln!(out, "{icon} {label}: {}", diff.name());
44
45 if unchanged {
46 out.push_str(" no drift\n");
47 if let ResourceDiff::CustomAttribute(d) = diff {
50 render_custom_attribute(out, d);
51 }
52 return;
53 }
54
55 match diff {
56 ResourceDiff::CatalogSchema(d) => render_catalog_schema(out, d),
57 ResourceDiff::ContentBlock(d) => render_content_block(out, d),
58 ResourceDiff::EmailTemplate(d) => render_email_template(out, d),
59 ResourceDiff::CustomAttribute(d) => render_custom_attribute(out, d),
60 }
61}
62
63fn kind_icon(kind: ResourceKind) -> &'static str {
64 match kind {
65 ResourceKind::CatalogSchema => "๐",
66 ResourceKind::ContentBlock => "๐",
67 ResourceKind::EmailTemplate => "๐ง",
68 ResourceKind::CustomAttribute => "๐ท ",
69 }
70}
71
72fn kind_label(kind: ResourceKind) -> &'static str {
73 match kind {
74 ResourceKind::CatalogSchema => "Catalog Schema",
75 ResourceKind::ContentBlock => "Content Block",
76 ResourceKind::EmailTemplate => "Email Template",
77 ResourceKind::CustomAttribute => "Custom Attribute",
78 }
79}
80
81fn fmt_field(f: &CatalogField) -> String {
82 format!("{} ({})", f.name, f.field_type.as_str())
83}
84
85fn render_catalog_schema(out: &mut String, d: &CatalogSchemaDiff) {
86 if matches!(d.op, DiffOp::Added(_)) {
87 out.push_str(" + new catalog\n");
88 } else if matches!(d.op, DiffOp::Removed(_)) {
89 out.push_str(" - removed catalog (destructive)\n");
90 }
91 for fd in &d.field_diffs {
92 match fd {
93 DiffOp::Added(f) => {
94 let _ = writeln!(out, " + field: {}", fmt_field(f));
95 }
96 DiffOp::Removed(f) => {
97 let _ = writeln!(out, " - field: {}", fmt_field(f));
98 }
99 DiffOp::Modified { from, to } => {
100 let _ = writeln!(
101 out,
102 " ~ field: {} ({} โ {})",
103 to.name,
104 from.field_type.as_str(),
105 to.field_type.as_str(),
106 );
107 }
108 DiffOp::Unchanged => {}
109 }
110 }
111}
112
113fn render_content_block(out: &mut String, d: &ContentBlockDiff) {
114 if d.orphan {
115 out.push_str(" โ orphaned (exists in Braze, not in Git)\n");
116 return;
117 }
118 match &d.op {
119 DiffOp::Added(_) => out.push_str(" + new content block\n"),
120 DiffOp::Removed(_) => out.push_str(" - removed content block\n"),
121 DiffOp::Modified { .. } => {
122 if let Some(td) = &d.text_diff {
123 let _ = writeln!(
124 out,
125 " ~ content changed (+{} -{})",
126 td.additions, td.deletions,
127 );
128 } else {
129 out.push_str(" ~ content changed\n");
130 }
131 }
132 DiffOp::Unchanged => {}
133 }
134}
135
136fn render_email_template(out: &mut String, d: &EmailTemplateDiff) {
137 if d.orphan {
138 out.push_str(" โ orphaned (exists in Braze, not in Git)\n");
139 return;
140 }
141 if matches!(d.op, DiffOp::Added(_)) {
142 out.push_str(" + new email template\n");
143 } else if matches!(d.op, DiffOp::Removed(_)) {
144 out.push_str(" - removed email template\n");
145 }
146 if d.subject_changed {
147 out.push_str(" ~ subject changed\n");
148 }
149 if let Some(td) = &d.body_html_diff {
150 let _ = writeln!(
151 out,
152 " ~ body_html changed (+{} -{})",
153 td.additions, td.deletions
154 );
155 }
156 if let Some(td) = &d.body_plaintext_diff {
157 let _ = writeln!(
158 out,
159 " ~ body_plaintext changed (+{} -{})",
160 td.additions, td.deletions
161 );
162 }
163 if d.metadata_changed {
164 out.push_str(" ~ metadata changed\n");
165 }
166}
167
168fn render_custom_attribute(out: &mut String, d: &CustomAttributeDiff) {
169 match &d.op {
170 CustomAttributeOp::DeprecationToggled { from, to } => {
171 let _ = writeln!(out, " ~ deprecated: {from} โ {to}");
172 }
173 CustomAttributeOp::UnregisteredInGit => {
174 out.push_str(" โ exists in Braze but not in Git registry (run export)\n");
175 }
176 CustomAttributeOp::PresentInGitOnly => {
177 out.push_str(" โ in Git registry but not in Braze (likely a typo)\n");
178 }
179 CustomAttributeOp::MetadataOnly => {
180 out.push_str(" ~ metadata-only change (no API to apply)\n");
181 }
182 CustomAttributeOp::Unchanged => {}
183 }
184 for hint in &d.hints {
185 let _ = writeln!(out, " โน {hint}");
186 }
187}