1use std::borrow::Cow;
2use std::collections::{HashMap, HashSet};
3
4use log::debug;
5use yaml_rust2::yaml::{Hash, Yaml};
6use crate::deep_equal::deep_equal;
7
8pub fn compute_diff<'a>(obj: &'a Yaml, helm: &'a Yaml) -> Option<Cow<'a, Yaml>> {
10 if deep_equal(obj, helm) {
11 None
12 } else {
13 match (obj, helm) {
14 (Yaml::Hash(obj_hash), Yaml::Hash(helm_hash)) => {
15 let mut diff_hash = Hash::new();
16 for (key, obj_value) in obj_hash {
17 let helm_value = helm_hash.get(key).unwrap_or(&Yaml::Null);
18 if let Some(diff_value) = compute_diff(obj_value, helm_value) {
19 diff_hash.insert(key.clone(), diff_value.into_owned());
20 }
21 }
22 if diff_hash.is_empty() {
23 None
24 } else {
25 Some(Cow::Owned(Yaml::Hash(diff_hash)))
26 }
27 }
28 (Yaml::Array(obj_array), Yaml::Array(helm_array)) => {
29 if obj_array.len() != helm_array.len() {
30 Some(Cow::Borrowed(obj))
31 } else {
32 let mut has_diff = false;
33 let diffs: Vec<_> = obj_array
34 .iter()
35 .zip(helm_array.iter())
36 .map(|(obj_item, helm_item)| {
37 if let Some(diff_item) = compute_diff(obj_item, helm_item) {
38 has_diff = true;
39 diff_item.into_owned()
40 } else {
41 Yaml::Null
42 }
43 })
44 .collect();
45
46 if has_diff {
47 Some(Cow::Owned(Yaml::Array(diffs)))
48 } else {
49 None
50 }
51 }
52 }
53 _ => Some(Cow::Borrowed(obj)),
54 }
55 }
56}
57
58pub fn diff_and_common_multiple<'a>(
60 objs: &'a [&'a Yaml],
61 quorum: f64,
62) -> (Option<Cow<'a, Yaml>>, Vec<Option<Cow<'a, Yaml>>>) {
63 debug!(
64 "diff_and_common_multiple called with {} objects and quorum {}%.",
65 objs.len(),
66 quorum * 100.0
67 );
68
69 if objs.is_empty() {
70 debug!("No objects to process. Returning.");
71 return (None, vec![]);
72 }
73
74 let total_files = objs.len();
75 let quorum_count = (quorum * total_files as f64).ceil() as usize;
76
77 let mut type_set = HashSet::new();
79 let mut obj_type = "";
80
81 for obj in objs {
82 let obj_type_str = match obj {
83 Yaml::Null => "null",
84 Yaml::Boolean(_) => "bool",
85 Yaml::Integer(_) => "int",
86 Yaml::Real(_) => "real",
87 Yaml::String(_) => "string",
88 Yaml::Array(_) => "array",
89 Yaml::Hash(_) => "hash",
90 _ => "unknown",
91 };
92 type_set.insert(obj_type_str);
93 if obj_type.is_empty() {
94 obj_type = obj_type_str;
95 }
96 }
97
98 if type_set.len() > 1 {
100 debug!("Types differ. Including entire values in diffs.");
101 return (
102 None,
103 objs.iter().map(|obj| Some(Cow::Borrowed(*obj))).collect(),
104 );
105 }
106
107 if obj_type != "hash" {
109 debug!("Handling primitive types or arrays as atomic units.");
110
111 let mut occurrences: HashMap<&Yaml, usize> = HashMap::new();
113 for obj in objs {
114 *occurrences.entry(*obj).or_insert(0) += 1;
115 }
116
117 let base_value = occurrences.iter().find_map(|(val, &count)| {
119 if count >= quorum_count {
120 Some(*val)
121 } else {
122 None
123 }
124 });
125
126 if let Some(base_val) = base_value {
127 debug!("Base value determined by quorum: {:?}", base_val);
128 let diffs = objs
129 .iter()
130 .map(|obj| {
131 if deep_equal(obj, base_val) {
132 None
133 } else {
134 Some(Cow::Borrowed(*obj))
135 }
136 })
137 .collect();
138 return (Some(Cow::Borrowed(base_val)), diffs);
139 } else {
140 debug!("No value meets the quorum; including all values in diffs.");
142 return (
143 None,
144 objs.iter().map(|obj| Some(Cow::Borrowed(*obj))).collect(),
145 );
146 }
147 }
148
149 if obj_type == "hash" {
151 debug!("Handling hashes (maps).");
152 let mut all_keys = HashSet::new();
154 for obj in objs {
155 if let Yaml::Hash(ref h) = obj {
156 all_keys.extend(h.keys());
157 }
158 }
159
160 let mut base_hash = Hash::new();
162 let mut diffs: Vec<Hash> = vec![Hash::new(); objs.len()];
163 let mut has_base = false;
164 let mut has_diffs = vec![false; objs.len()];
165
166 for key in &all_keys {
168 debug!("Processing key: {:?}", key);
169
170 let values_at_key: Vec<&Yaml> = objs
172 .iter()
173 .map(|obj| {
174 if let Yaml::Hash(ref h) = obj {
175 h.get(*key).unwrap_or(&Yaml::Null)
176 } else {
177 &Yaml::Null
178 }
179 })
180 .collect();
181
182 let (sub_base, sub_diffs) = diff_and_common_multiple(&values_at_key, quorum);
184
185 if let Some(ref sub_base_val) = sub_base {
186 base_hash.insert((*key).clone(), sub_base_val.clone().into_owned());
188 has_base = true;
189 }
190
191 let base_includes_key = sub_base.is_some();
192
193 for (i, sub_diff) in sub_diffs.into_iter().enumerate() {
194 if let Some(sub_diff_val) = sub_diff {
195 if !sub_diff_val.is_null() || base_includes_key {
196 diffs[i].insert((*key).clone(), sub_diff_val.into_owned());
197 has_diffs[i] = true;
198 }
199 }
200 }
201 }
202
203 let base = if has_base {
205 Some(Cow::Owned(Yaml::Hash(base_hash)))
206 } else {
207 None
208 };
209 let diffs_result: Vec<Option<Cow<'a, Yaml>>> = diffs
210 .into_iter()
211 .enumerate()
212 .map(|(i, h)| {
213 if has_diffs[i] {
214 Some(Cow::Owned(Yaml::Hash(h)))
215 } else {
216 None
217 }
218 })
219 .collect();
220
221 return (base, diffs_result);
222 }
223
224 debug!("Unhandled object type. Including entire values in diffs.");
226 (
227 None,
228 objs.iter().map(|obj| Some(Cow::Borrowed(*obj))).collect(),
229 )
230}