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;
16
17#[derive(Debug, Clone)]
18pub struct TextDiffSummary {
19 pub additions: usize,
20 pub deletions: usize,
21}
22
23pub(crate) fn compute_text_diff(from: &str, to: &str) -> TextDiffSummary {
24 let diff = TextDiff::from_lines(from, to);
25 let mut additions = 0;
26 let mut deletions = 0;
27 for change in diff.iter_all_changes() {
28 match change.tag() {
29 ChangeTag::Insert => additions += 1,
30 ChangeTag::Delete => deletions += 1,
31 ChangeTag::Equal => {}
32 }
33 }
34 TextDiffSummary {
35 additions,
36 deletions,
37 }
38}
39
40pub(crate) fn opt_str_eq(a: &Option<String>, b: &Option<String>) -> bool {
43 a.as_deref().unwrap_or("") == b.as_deref().unwrap_or("")
44}
45
46pub(crate) fn tags_eq_unordered(a: &[String], b: &[String]) -> bool {
48 if a.len() != b.len() {
49 return false;
50 }
51 let mut a: Vec<&str> = a.iter().map(String::as_str).collect();
52 let mut b: Vec<&str> = b.iter().map(String::as_str).collect();
53 a.sort_unstable();
54 b.sort_unstable();
55 a == b
56}
57
58#[derive(Debug, Clone, PartialEq)]
61pub enum DiffOp<T> {
62 Added(T),
63 Removed(T),
64 Modified { from: T, to: T },
65 Unchanged,
66}
67
68impl<T> DiffOp<T> {
69 pub fn is_change(&self) -> bool {
70 !matches!(self, Self::Unchanged)
71 }
72
73 pub fn is_destructive(&self) -> bool {
74 matches!(self, Self::Removed(_))
75 }
76}
77
78#[derive(Debug, Clone)]
80pub enum ResourceDiff {
81 CatalogSchema(catalog::CatalogSchemaDiff),
82 CatalogItems(catalog::CatalogItemsDiff),
83 ContentBlock(content_block::ContentBlockDiff),
84 EmailTemplate(email_template::EmailTemplateDiff),
85 CustomAttribute(custom_attribute::CustomAttributeDiff),
86}
87
88impl ResourceDiff {
89 pub fn kind(&self) -> ResourceKind {
90 match self {
91 Self::CatalogSchema(_) => ResourceKind::CatalogSchema,
92 Self::CatalogItems(_) => ResourceKind::CatalogItems,
93 Self::ContentBlock(_) => ResourceKind::ContentBlock,
94 Self::EmailTemplate(_) => ResourceKind::EmailTemplate,
95 Self::CustomAttribute(_) => ResourceKind::CustomAttribute,
96 }
97 }
98
99 pub fn name(&self) -> &str {
100 match self {
101 Self::CatalogSchema(d) => &d.name,
102 Self::CatalogItems(d) => &d.catalog_name,
103 Self::ContentBlock(d) => &d.name,
104 Self::EmailTemplate(d) => &d.name,
105 Self::CustomAttribute(d) => &d.name,
106 }
107 }
108
109 pub fn has_changes(&self) -> bool {
110 match self {
111 Self::CatalogSchema(d) => d.has_changes(),
112 Self::CatalogItems(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 }
117 }
118
119 pub fn is_actionable(&self) -> bool {
125 match self {
126 Self::CustomAttribute(d) => d.is_actionable(),
127 other => other.has_changes(),
128 }
129 }
130
131 pub fn has_destructive(&self) -> bool {
132 match self {
133 Self::CatalogSchema(d) => d.has_destructive(),
134 Self::CatalogItems(d) => d.has_destructive(),
135 Self::ContentBlock(_) => false,
139 Self::EmailTemplate(_) => false,
140 Self::CustomAttribute(_) => false,
142 }
143 }
144
145 pub fn is_orphan(&self) -> bool {
146 match self {
147 Self::ContentBlock(d) => d.is_orphan(),
148 Self::EmailTemplate(d) => d.is_orphan(),
149 _ => false,
150 }
151 }
152}
153
154#[derive(Debug, Clone, Default)]
155pub struct DiffSummary {
156 pub diffs: Vec<ResourceDiff>,
157}
158
159impl DiffSummary {
160 pub fn changed_count(&self) -> usize {
161 self.diffs.iter().filter(|d| d.has_changes()).count()
162 }
163
164 pub fn actionable_count(&self) -> usize {
167 self.diffs.iter().filter(|d| d.is_actionable()).count()
168 }
169
170 pub fn destructive_count(&self) -> usize {
171 self.diffs.iter().filter(|d| d.has_destructive()).count()
172 }
173
174 pub fn orphan_count(&self) -> usize {
175 self.diffs.iter().filter(|d| d.is_orphan()).count()
176 }
177
178 pub fn in_sync_count(&self) -> usize {
179 self.diffs.iter().filter(|d| !d.has_changes()).count()
180 }
181}