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