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