1use crate::resource::ResourceKind;
9use similar::{ChangeTag, TextDiff};
10
11pub mod catalog;
12pub mod content_block;
13pub mod content_block_order;
14pub mod custom_attribute;
15pub mod email_template;
16pub mod orphan;
17pub mod tag;
18
19#[derive(Debug, Clone)]
20pub struct TextDiffSummary {
21 pub additions: usize,
22 pub deletions: usize,
23}
24
25pub(crate) fn compute_text_diff(from: &str, to: &str) -> TextDiffSummary {
26 let diff = TextDiff::from_lines(from, to);
27 let mut additions = 0;
28 let mut deletions = 0;
29 for change in diff.iter_all_changes() {
30 match change.tag() {
31 ChangeTag::Insert => additions += 1,
32 ChangeTag::Delete => deletions += 1,
33 ChangeTag::Equal => {}
34 }
35 }
36 TextDiffSummary {
37 additions,
38 deletions,
39 }
40}
41
42pub(crate) fn opt_str_eq(a: &Option<String>, b: &Option<String>) -> bool {
45 a.as_deref().unwrap_or("") == b.as_deref().unwrap_or("")
46}
47
48pub(crate) fn tags_eq_unordered(a: &[String], b: &[String]) -> bool {
50 if a.len() != b.len() {
51 return false;
52 }
53 let mut a: Vec<&str> = a.iter().map(String::as_str).collect();
54 let mut b: Vec<&str> = b.iter().map(String::as_str).collect();
55 a.sort_unstable();
56 b.sort_unstable();
57 a == b
58}
59
60#[derive(Debug, Clone, PartialEq)]
63pub enum DiffOp<T> {
64 Added(T),
65 Removed(T),
66 Modified { from: T, to: T },
67 Unchanged,
68}
69
70impl<T> DiffOp<T> {
71 pub fn is_change(&self) -> bool {
72 !matches!(self, Self::Unchanged)
73 }
74
75 pub fn is_destructive(&self) -> bool {
76 matches!(self, Self::Removed(_))
77 }
78}
79
80#[derive(Debug, Clone)]
82pub enum ResourceDiff {
83 CatalogSchema(catalog::CatalogSchemaDiff),
84 ContentBlock(content_block::ContentBlockDiff),
85 EmailTemplate(email_template::EmailTemplateDiff),
86 CustomAttribute(custom_attribute::CustomAttributeDiff),
87 Tag(tag::TagDiff),
88}
89
90impl ResourceDiff {
91 pub fn kind(&self) -> ResourceKind {
92 match self {
93 Self::CatalogSchema(_) => ResourceKind::CatalogSchema,
94 Self::ContentBlock(_) => ResourceKind::ContentBlock,
95 Self::EmailTemplate(_) => ResourceKind::EmailTemplate,
96 Self::CustomAttribute(_) => ResourceKind::CustomAttribute,
97 Self::Tag(_) => ResourceKind::Tag,
98 }
99 }
100
101 pub fn name(&self) -> &str {
102 match self {
103 Self::CatalogSchema(d) => &d.name,
104 Self::ContentBlock(d) => &d.name,
105 Self::EmailTemplate(d) => &d.name,
106 Self::CustomAttribute(d) => &d.name,
107 Self::Tag(d) => &d.name,
108 }
109 }
110
111 pub fn has_changes(&self) -> bool {
112 match self {
113 Self::CatalogSchema(d) => d.has_changes(),
114 Self::ContentBlock(d) => d.has_changes(),
115 Self::EmailTemplate(d) => d.has_changes(),
116 Self::CustomAttribute(d) => d.has_changes(),
117 Self::Tag(d) => d.has_changes(),
118 }
119 }
120
121 pub fn is_actionable(&self) -> bool {
129 match self {
130 Self::CustomAttribute(d) => d.is_actionable(),
131 Self::Tag(_) => false,
136 other => other.has_changes(),
137 }
138 }
139
140 pub fn has_destructive(&self) -> bool {
141 match self {
142 Self::CatalogSchema(d) => d.has_destructive(),
143 Self::ContentBlock(_) => false,
147 Self::EmailTemplate(_) => false,
148 Self::CustomAttribute(_) => false,
150 Self::Tag(_) => false,
152 }
153 }
154
155 pub fn is_orphan(&self) -> bool {
156 match self {
157 Self::ContentBlock(d) => d.is_orphan(),
158 Self::EmailTemplate(d) => d.is_orphan(),
159 _ => false,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Default)]
165pub struct DiffSummary {
166 pub diffs: Vec<ResourceDiff>,
167}
168
169impl DiffSummary {
170 pub fn changed_count(&self) -> usize {
171 self.diffs.iter().filter(|d| d.has_changes()).count()
172 }
173
174 pub fn actionable_count(&self) -> usize {
177 self.diffs.iter().filter(|d| d.is_actionable()).count()
178 }
179
180 pub fn destructive_count(&self) -> usize {
181 self.diffs.iter().filter(|d| d.has_destructive()).count()
182 }
183
184 pub fn orphan_count(&self) -> usize {
185 self.diffs.iter().filter(|d| d.is_orphan()).count()
186 }
187
188 pub fn in_sync_count(&self) -> usize {
189 self.diffs.iter().filter(|d| !d.has_changes()).count()
190 }
191}