1use std::collections::{HashMap, HashSet};
2
3use facet::{Def, Shape, StructKind, Type, UserType};
4use facet_core::Facet;
5use facet_reflect::{HasFields, Peek};
6
7use crate::sequences::{self, Updates};
8
9pub enum Diff<'mem, 'facet> {
13 Equal,
15
16 Replace {
20 from: Peek<'mem, 'facet>,
22
23 to: Peek<'mem, 'facet>,
25 },
26
27 User {
29 from: &'static Shape,
31
32 to: &'static Shape,
34
35 variant: Option<&'static str>,
37
38 value: Value<'mem, 'facet>,
40 },
41
42 Sequence {
44 from: &'static Shape,
46
47 to: &'static Shape,
49
50 updates: Updates<'mem, 'facet>,
52 },
53}
54
55pub enum Value<'mem, 'facet> {
57 Tuple {
58 updates: Updates<'mem, 'facet>,
60 },
61
62 Struct {
63 updates: HashMap<&'static str, Diff<'mem, 'facet>>,
65
66 deletions: HashMap<&'static str, Peek<'mem, 'facet>>,
68
69 insertions: HashMap<&'static str, Peek<'mem, 'facet>>,
71
72 unchanged: HashSet<&'static str>,
74 },
75}
76
77impl<'mem, 'facet> Value<'mem, 'facet> {
78 fn closeness(&self) -> usize {
79 match self {
80 Self::Tuple { updates } => updates.closeness(),
81 Self::Struct { unchanged, .. } => unchanged.len(),
82 }
83 }
84}
85
86pub trait FacetDiff<'f>: Facet<'f> {
88 fn diff<'a, U: Facet<'f>>(&'a self, other: &'a U) -> Diff<'a, 'f>;
90}
91
92impl<'f, T: Facet<'f>> FacetDiff<'f> for T {
93 fn diff<'a, U: Facet<'f>>(&'a self, other: &'a U) -> Diff<'a, 'f> {
94 Diff::new(self, other)
95 }
96}
97
98impl<'mem, 'facet> Diff<'mem, 'facet> {
99 pub fn is_equal(&self) -> bool {
101 matches!(self, Self::Equal)
102 }
103
104 pub fn new<T: Facet<'facet>, U: Facet<'facet>>(from: &'mem T, to: &'mem U) -> Self {
106 Self::new_peek(Peek::new(from), Peek::new(to))
107 }
108
109 pub(crate) fn new_peek(from: Peek<'mem, 'facet>, to: Peek<'mem, 'facet>) -> Self {
110 if from.shape().id == to.shape().id && from.shape().is_partial_eq() && from == to {
111 return Diff::Equal;
112 }
113
114 match (
115 (from.shape().def, from.shape().ty),
116 (to.shape().def, to.shape().ty),
117 ) {
118 (
119 (_, Type::User(UserType::Struct(from_ty))),
120 (_, Type::User(UserType::Struct(to_ty))),
121 ) if from_ty.kind == to_ty.kind => {
122 let from_ty = from.into_struct().unwrap();
123 let to_ty = to.into_struct().unwrap();
124
125 let value =
126 if [StructKind::Tuple, StructKind::TupleStruct].contains(&from_ty.ty().kind) {
127 let from = from_ty.fields().map(|x| x.1).collect();
128 let to = to_ty.fields().map(|x| x.1).collect();
129
130 let updates = sequences::diff(from, to);
131
132 Value::Tuple { updates }
133 } else {
134 let mut updates = HashMap::new();
135 let mut deletions = HashMap::new();
136 let mut insertions = HashMap::new();
137 let mut unchanged = HashSet::new();
138
139 for (field, from) in from_ty.fields() {
140 if let Ok(to) = to_ty.field_by_name(field.name) {
141 let diff = Diff::new_peek(from, to);
142 if diff.is_equal() {
143 unchanged.insert(field.name);
144 } else {
145 updates.insert(field.name, diff);
146 }
147 } else {
148 deletions.insert(field.name, from);
149 }
150 }
151
152 for (field, to) in to_ty.fields() {
153 if from_ty.field_by_name(field.name).is_err() {
154 insertions.insert(field.name, to);
155 }
156 }
157 Value::Struct {
158 updates,
159 deletions,
160 insertions,
161 unchanged,
162 }
163 };
164
165 Diff::User {
166 from: from.shape(),
167 to: to.shape(),
168 variant: None,
169 value,
170 }
171 }
172 ((_, Type::User(UserType::Enum(_))), (_, Type::User(UserType::Enum(_)))) => {
173 let from_enum = from.into_enum().unwrap();
174 let to_enum = to.into_enum().unwrap();
175
176 let from_variant = from_enum.active_variant().unwrap();
177 let to_variant = to_enum.active_variant().unwrap();
178
179 if from_variant.name != to_variant.name
180 || from_variant.data.kind != to_variant.data.kind
181 {
182 return Diff::Replace { from, to };
183 }
184
185 let value = if [StructKind::Tuple, StructKind::TupleStruct]
186 .contains(&from_variant.data.kind)
187 {
188 let from = from_enum.fields().map(|x| x.1).collect();
189 let to = to_enum.fields().map(|x| x.1).collect();
190
191 let updates = sequences::diff(from, to);
192
193 Value::Tuple { updates }
194 } else {
195 let mut updates = HashMap::new();
196 let mut deletions = HashMap::new();
197 let mut insertions = HashMap::new();
198 let mut unchanged = HashSet::new();
199
200 for (field, from) in from_enum.fields() {
201 if let Ok(Some(to)) = to_enum.field_by_name(field.name) {
202 let diff = Diff::new_peek(from, to);
203 if diff.is_equal() {
204 unchanged.insert(field.name);
205 } else {
206 updates.insert(field.name, diff);
207 }
208 } else {
209 deletions.insert(field.name, from);
210 }
211 }
212
213 for (field, to) in to_enum.fields() {
214 if !from_enum
215 .field_by_name(field.name)
216 .is_ok_and(|x| x.is_some())
217 {
218 insertions.insert(field.name, to);
219 }
220 }
221
222 Value::Struct {
223 updates,
224 deletions,
225 insertions,
226 unchanged,
227 }
228 };
229
230 Diff::User {
231 from: from_enum.shape(),
232 to: to_enum.shape(),
233 variant: Some(from_variant.name),
234 value,
235 }
236 }
237 ((Def::Option(_), _), (Def::Option(_), _)) => {
238 let from_option = from.into_option().unwrap();
239 let to_option = to.into_option().unwrap();
240
241 let (Some(from_value), Some(to_value)) = (from_option.value(), to_option.value())
242 else {
243 return Diff::Replace { from, to };
244 };
245
246 let mut updates = Updates::default();
247
248 let diff = Self::new_peek(from_value, to_value);
249 if !diff.is_equal() {
250 updates.push_add(to_value);
251 updates.push_remove(from_value);
252 }
253
254 Diff::User {
255 from: from.shape(),
256 to: to.shape(),
257 variant: Some("Some"),
258 value: Value::Tuple { updates },
259 }
260 }
261 (
262 (Def::List(_), _) | (_, Type::Sequence(_)),
263 (Def::List(_), _) | (_, Type::Sequence(_)),
264 ) => {
265 let from_list = from.into_list_like().unwrap();
266 let to_list = to.into_list_like().unwrap();
267
268 let updates = sequences::diff(
269 from_list.iter().collect::<Vec<_>>(),
270 to_list.iter().collect::<Vec<_>>(),
271 );
272
273 Diff::Sequence {
274 from: from.shape(),
275 to: to.shape(),
276 updates,
277 }
278 }
279 _ => Diff::Replace { from, to },
280 }
281 }
282
283 pub(crate) fn closeness(&self) -> usize {
284 match self {
285 Self::Equal => 1, Self::Replace { .. } => 0,
287 Self::Sequence { updates, .. } => updates.closeness(),
288 Self::User {
289 from, to, value, ..
290 } => value.closeness() + (from == to) as usize,
291 }
292 }
293}