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