1use std::fmt;
2
3use facet::Facet;
4use facet_value::Value;
5
6use crate::{NameSet, Report};
7
8#[derive(Clone, Debug, Default, Eq, PartialEq)]
10pub struct FieldValue(Value);
11
12impl FieldValue {
13 #[must_use]
15 pub fn of<'a, T: Facet<'a>>(value: &T) -> Self {
16 Self(facet_value::to_value(value).unwrap_or_default())
17 }
18
19 #[must_use]
21 pub fn text<D: fmt::Display>(value: &D) -> Self {
22 Self(Value::from(value.to_string()))
23 }
24}
25
26impl fmt::Display for FieldValue {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 if self.0.is_null() {
29 return f.write_str("none");
30 }
31 if let Some(text) = self.0.as_string() {
32 return f.write_str(text.as_str());
33 }
34 match facet_json::to_string(&self.0) {
35 Ok(json) => f.write_str(&json),
36 Err(_) => f.write_str("?"),
37 }
38 }
39}
40
41#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct FieldChange {
44 pub name: &'static str,
46 pub old: FieldValue,
48 pub new: FieldValue,
50}
51
52impl fmt::Display for FieldChange {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "{}: {} -> {}", self.name, self.old, self.new)
55 }
56}
57
58#[derive(Clone, Debug, Eq, PartialEq)]
60pub enum Change {
61 Create,
63 Delete,
65 Update(Vec<FieldChange>),
67}
68
69impl Change {
70 #[must_use]
72 pub fn fields(&self) -> Vec<&'static str> {
73 match self {
74 Self::Create | Self::Delete => Vec::new(),
75 Self::Update(fields) => fields.iter().map(|field| field.name).collect(),
76 }
77 }
78
79 #[must_use]
81 pub fn field(name: &'static str, old: FieldValue, new: FieldValue) -> Self {
82 Self::Update(vec![FieldChange { name, old, new }])
83 }
84
85 pub fn lines<'a>(&'a self, resource: &'a str) -> impl Iterator<Item = String> + 'a {
87 let lines: Vec<String> = match self {
88 Self::Create => vec![format!("{resource}: create")],
89 Self::Delete => vec![format!("{resource}: delete")],
90 Self::Update(fields) => fields
91 .iter()
92 .map(|field| format!("{resource}: {field}"))
93 .collect(),
94 };
95 lines.into_iter()
96 }
97}
98
99#[derive(Clone, Debug, Default)]
102pub struct FieldDiff {
103 changed: Vec<FieldChange>,
104 same: Vec<&'static str>,
105}
106
107impl FieldDiff {
108 pub fn check<'a, O: Facet<'a>, N: Facet<'a>>(
110 &mut self,
111 name: &'static str,
112 equal: bool,
113 old: &O,
114 new: &N,
115 ) -> bool {
116 if equal {
117 self.same.push(name);
118 } else {
119 self.changed.push(FieldChange {
120 name,
121 old: FieldValue::of(old),
122 new: FieldValue::of(new),
123 });
124 }
125 !equal
126 }
127
128 pub fn compare<'a, T: Facet<'a> + PartialEq>(
130 &mut self,
131 name: &'static str,
132 old: Option<&'a T>,
133 new: &T,
134 ) -> bool {
135 self.check(name, old == Some(new), &old, new)
136 }
137
138 pub fn want<'a, T, D, L>(&mut self, name: &'static str, desired: D, live: L) -> Option<T>
143 where
144 T: Facet<'a> + Clone + PartialEq,
145 D: Into<Option<&'a T>>,
146 L: Into<Option<&'a T>>,
147 {
148 let wanted = desired.into()?;
149 let live = live.into();
150 self.check(name, live == Some(wanted), &live, wanted)
151 .then(|| wanted.clone())
152 }
153
154 pub fn want_set(
159 &mut self,
160 name: &'static str,
161 desired: &NameSet,
162 live: Option<&NameSet>,
163 ) -> Option<NameSet> {
164 let empty = NameSet::default();
165 let live = live.unwrap_or(&empty);
166 self.check(name, live == desired, &live.as_slice(), &desired.as_slice())
167 .then(|| desired.clone())
168 }
169
170 #[must_use]
172 pub const fn has_changes(&self) -> bool {
173 !self.changed.is_empty()
174 }
175
176 pub fn record(mut self, resource: &str, report: &mut Report) {
178 self.changed.sort_unstable_by_key(|field| field.name);
179 self.same.sort_unstable();
180 if !self.changed.is_empty() {
181 report
182 .changes
183 .insert(resource.to_owned(), Change::Update(self.changed));
184 }
185 if !self.same.is_empty() {
186 report.unchanged.insert(
187 resource.to_owned(),
188 self.same.into_iter().map(str::to_owned).collect(),
189 );
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn an_update_lists_each_changed_field_with_both_values() {
200 let mut diff = FieldDiff::default();
201 diff.check("enforcement", false, &"evaluate", &"active");
202 diff.check("target", true, &"branch", &"branch");
203 diff.check("bypass_actors", false, &Vec::<u32>::new(), &vec![1u32, 2]);
204 diff.check("homepage", false, &None::<String>, &Some("x".to_owned()));
205 let mut report = Report::default();
206 diff.record("ruleset main", &mut report);
207 assert_eq!(
208 report.lines().collect::<Vec<_>>(),
209 [
210 "ruleset main: bypass_actors: [] -> [1,2]",
211 "ruleset main: enforcement: evaluate -> active",
212 "ruleset main: homepage: none -> x",
213 ]
214 );
215 assert_eq!(report.unchanged["ruleset main"], ["target"]);
216 }
217
218 #[test]
219 fn want_returns_a_declared_value_that_differs() {
220 let mut diff = FieldDiff::default();
221 assert_eq!(diff.want("a", Some(&1), Some(&2)), Some(1));
222 assert_eq!(diff.want("b", Some(&1), Some(&1)), None);
223 assert_eq!(diff.want("c", None::<&i32>, Some(&1)), None);
224 assert_eq!(diff.want("d", Some(&1), None), Some(1));
225 let mut report = Report::default();
226 diff.record("thing", &mut report);
227 assert_eq!(report.unchanged["thing"], ["b"]);
228 }
229
230 #[test]
231 fn want_set_ignores_order_and_a_missing_live_set_is_empty() {
232 let mut diff = FieldDiff::default();
233 let wanted = NameSet::new(["b".to_owned(), "a".to_owned()]);
234 let live = NameSet::new(["a".to_owned(), "b".to_owned()]);
235 assert_eq!(diff.want_set("same", &wanted, Some(&live)), None);
236 assert_eq!(
237 diff.want_set("missing", &wanted, None).as_ref(),
238 Some(&wanted)
239 );
240 assert_eq!(diff.want_set("empty", &NameSet::default(), None), None);
241 }
242
243 #[test]
244 fn create_and_delete_print_one_word() {
245 let mut report = Report::default();
246 report.changes.insert("label a".to_owned(), Change::Create);
247 report.record_delete("label b".to_owned());
248 assert_eq!(
249 report.lines().collect::<Vec<_>>(),
250 ["label a: create", "label b: delete"]
251 );
252 }
253}