1use std::borrow::Cow;
15
16use tracing::debug;
17
18use facet_core::{Def, NumericType, PrimitiveType, Shape, StructKind, TextualType, Type, UserType};
19use facet_reflect::Peek;
20use indextree::{Arena, NodeId};
21
22use super::{
23 Attr, DiffFlavor, ElementChange, FormatArena, FormattedValue, Layout, LayoutNode, ValueType,
24 group_changed_attrs,
25};
26use crate::{Diff, ReplaceGroup, Updates, UpdatesGroup, Value};
27
28fn get_shape_display_name(shape: &Shape) -> &'static str {
30 if let Some(renamed) = shape.get_builtin_attr_value::<&str>("rename") {
31 return renamed;
32 }
33 shape.type_identifier
34}
35
36fn shape_has_xml_attrs(shape: &Shape) -> bool {
40 shape.attributes.iter().any(|attr| attr.ns == Some("xml"))
41}
42
43fn get_xml_display_name(shape: &Shape) -> Cow<'static, str> {
48 let base_name = get_shape_display_name(shape);
49
50 if let Type::User(UserType::Struct(_)) = shape.ty
52 && !shape_has_xml_attrs(shape)
53 {
54 return Cow::Owned(format!("@{}", base_name));
55 }
56
57 Cow::Borrowed(base_name)
58}
59
60fn get_variant_display_name(variant: &facet_core::Variant) -> &'static str {
62 if let Some(attr) = variant.get_builtin_attr("rename")
63 && let Some(renamed) = attr.get_as::<&'static str>()
64 {
65 return renamed;
66 }
67 variant.name
68}
69
70fn should_skip_falsy(peek: Peek<'_, '_>) -> bool {
75 let shape = peek.shape();
76 if let Some(truthy_fn) = shape.truthiness_fn() {
78 let is_truthy = unsafe { truthy_fn(peek.data()) };
80 let should_skip = !is_truthy;
81 debug!(
82 type_id = %shape.type_identifier,
83 is_truthy,
84 should_skip,
85 "should_skip_falsy check"
86 );
87 return should_skip;
88 }
89 false
90}
91
92fn determine_value_type(peek: Peek<'_, '_>) -> ValueType {
94 let shape = peek.shape();
95
96 if let Def::Option(_) = shape.def {
98 if let Ok(opt) = peek.into_option() {
100 if opt.is_none() {
101 return ValueType::Null;
102 }
103 if let Some(inner) = opt.value() {
105 return determine_value_type(inner);
106 }
107 }
108 return ValueType::Other;
109 }
110
111 match shape.ty {
113 Type::Primitive(p) => match p {
114 PrimitiveType::Boolean => ValueType::Boolean,
115 PrimitiveType::Numeric(NumericType::Integer { .. })
116 | PrimitiveType::Numeric(NumericType::Float) => ValueType::Number,
117 PrimitiveType::Textual(TextualType::Char)
118 | PrimitiveType::Textual(TextualType::Str) => ValueType::String,
119 PrimitiveType::Never => ValueType::Null,
120 },
121 _ => ValueType::Other,
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct BuildOptions {
128 pub max_line_width: usize,
130 pub max_unchanged_fields: usize,
133 pub collapse_threshold: usize,
135 pub float_precision: Option<usize>,
139}
140
141impl Default for BuildOptions {
142 fn default() -> Self {
143 Self {
144 max_line_width: 80,
145 max_unchanged_fields: 5,
146 collapse_threshold: 3,
147 float_precision: None,
148 }
149 }
150}
151
152impl BuildOptions {
153 pub fn with_float_precision(mut self, precision: usize) -> Self {
159 self.float_precision = Some(precision);
160 self
161 }
162}
163
164pub fn build_layout<'mem, 'facet, F: DiffFlavor>(
176 diff: &Diff<'mem, 'facet>,
177 from: Peek<'mem, 'facet>,
178 to: Peek<'mem, 'facet>,
179 opts: &BuildOptions,
180 flavor: &F,
181) -> Layout {
182 let mut builder = LayoutBuilder::new(opts.clone(), flavor);
183 let root_id = builder.build(diff, Some(from), Some(to));
184 builder.finish(root_id)
185}
186
187struct LayoutBuilder<'f, F: DiffFlavor> {
189 strings: FormatArena,
191 tree: Arena<LayoutNode>,
193 opts: BuildOptions,
195 flavor: &'f F,
197}
198
199impl<'f, F: DiffFlavor> LayoutBuilder<'f, F> {
200 fn new(opts: BuildOptions, flavor: &'f F) -> Self {
201 Self {
202 strings: FormatArena::new(),
203 tree: Arena::new(),
204 opts,
205 flavor,
206 }
207 }
208
209 fn build<'mem, 'facet>(
211 &mut self,
212 diff: &Diff<'mem, 'facet>,
213 from: Option<Peek<'mem, 'facet>>,
214 to: Option<Peek<'mem, 'facet>>,
215 ) -> NodeId {
216 self.build_diff(diff, from, to, ElementChange::None)
217 }
218
219 fn build_diff<'mem, 'facet>(
221 &mut self,
222 diff: &Diff<'mem, 'facet>,
223 from: Option<Peek<'mem, 'facet>>,
224 to: Option<Peek<'mem, 'facet>>,
225 change: ElementChange,
226 ) -> NodeId {
227 match diff {
228 Diff::Equal { value } => {
229 if let Some(peek) = value {
231 self.build_peek(*peek, ElementChange::None)
232 } else {
233 let (span, width) = self.strings.push_str("(equal)");
235 let value = FormattedValue::new(span, width);
236 self.tree.new_node(LayoutNode::Text {
237 value,
238 change: ElementChange::None,
239 })
240 }
241 }
242 Diff::Replace { from, to } => {
243 let root = self.tree.new_node(LayoutNode::element("_replace"));
245
246 let from_node = self.build_peek(*from, ElementChange::Deleted);
247 let to_node = self.build_peek(*to, ElementChange::Inserted);
248
249 root.append(from_node, &mut self.tree);
250 root.append(to_node, &mut self.tree);
251
252 root
253 }
254 Diff::User {
255 from: from_shape,
256 to: _to_shape,
257 variant,
258 value,
259 } => {
260 if matches!(from_shape.def, Def::Option(_))
263 && let Value::Tuple { updates } = value
264 {
265 let inner_from =
267 from.and_then(|p| p.into_option().ok().and_then(|opt| opt.value()));
268 let inner_to =
269 to.and_then(|p| p.into_option().ok().and_then(|opt| opt.value()));
270
271 return self.build_tuple_transparent(updates, inner_from, inner_to, change);
274 }
275
276 if let Some(variant_name) = *variant
279 && let Type::User(UserType::Enum(enum_ty)) = from_shape.ty
280 {
281 let tag =
283 if let Some(v) = enum_ty.variants.iter().find(|v| v.name == variant_name) {
284 Cow::Borrowed(get_variant_display_name(v))
285 } else {
286 Cow::Borrowed(variant_name)
287 };
288 debug!(
289 tag = tag.as_ref(),
290 variant_name, "Diff::User enum variant - using variant tag"
291 );
292
293 if let Value::Tuple { updates } = value {
295 let inner_from = from.and_then(|p| {
297 p.into_enum().ok().and_then(|e| e.field(0).ok().flatten())
298 });
299 let inner_to = to.and_then(|p| {
300 p.into_enum().ok().and_then(|e| e.field(0).ok().flatten())
301 });
302
303 return self
305 .build_enum_tuple_variant(tag, updates, inner_from, inner_to, change);
306 }
307
308 if let Value::Struct {
310 updates,
311 deletions,
312 insertions,
313 unchanged,
314 } = value
315 {
316 return self.build_struct(
317 tag, None, updates, deletions, insertions, unchanged, from, to, change,
318 );
319 }
320 }
321
322 let tag = get_xml_display_name(from_shape);
325 debug!(tag = tag.as_ref(), variant = ?variant, value_type = ?std::mem::discriminant(value), "Diff::User");
326
327 match value {
328 Value::Struct {
329 updates,
330 deletions,
331 insertions,
332 unchanged,
333 } => self.build_struct(
334 tag, *variant, updates, deletions, insertions, unchanged, from, to, change,
335 ),
336 Value::Tuple { updates } => {
337 debug!(tag = tag.as_ref(), "Value::Tuple - building tuple");
338 self.build_tuple(tag, *variant, updates, from, to, change)
339 }
340 }
341 }
342 Diff::Sequence {
343 from: _seq_shape_from,
344 to: _seq_shape_to,
345 updates,
346 } => {
347 let item_type = from
349 .and_then(|p| p.into_list_like().ok())
350 .and_then(|list| list.iter().next())
351 .or_else(|| {
352 to.and_then(|p| p.into_list_like().ok())
353 .and_then(|list| list.iter().next())
354 })
355 .map(|item| get_shape_display_name(item.shape()))
356 .unwrap_or("item");
357 self.build_sequence(updates, change, item_type)
358 }
359 }
360 }
361
362 fn build_peek(&mut self, peek: Peek<'_, '_>, change: ElementChange) -> NodeId {
364 let shape = peek.shape();
365 debug!(
366 type_id = %shape.type_identifier,
367 def = ?shape.def,
368 change = ?change,
369 "build_peek"
370 );
371
372 match (shape.def, shape.ty) {
374 (Def::Option(_), _) => {
376 if let Ok(opt) = peek.into_option()
377 && let Some(inner) = opt.value()
378 {
379 return self.build_peek(inner, change);
381 }
382 let (span, width) = self.strings.push_str("null");
384 return self.tree.new_node(LayoutNode::Text {
385 value: FormattedValue::with_type(span, width, ValueType::Null),
386 change,
387 });
388 }
389 (_, Type::User(UserType::Struct(ty))) if ty.kind == StructKind::Struct => {
390 if let Ok(struct_peek) = peek.into_struct() {
392 let tag = get_xml_display_name(shape);
393 let mut attrs = Vec::new();
394
395 for (i, field) in ty.fields.iter().enumerate() {
396 if let Ok(field_value) = struct_peek.field(i) {
397 if should_skip_falsy(field_value) {
399 continue;
400 }
401 let formatted_value = self.format_peek(field_value);
402 let attr = match change {
403 ElementChange::None => {
404 Attr::unchanged(field.name, field.name.len(), formatted_value)
405 }
406 ElementChange::Deleted => {
407 Attr::deleted(field.name, field.name.len(), formatted_value)
408 }
409 ElementChange::Inserted => {
410 Attr::inserted(field.name, field.name.len(), formatted_value)
411 }
412 ElementChange::MovedFrom | ElementChange::MovedTo => {
413 Attr::unchanged(field.name, field.name.len(), formatted_value)
415 }
416 };
417 attrs.push(attr);
418 }
419 }
420
421 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
422
423 return self.tree.new_node(LayoutNode::Element {
424 tag,
425 field_name: None,
426 attrs,
427 changed_groups,
428 change,
429 });
430 }
431 }
432 (_, Type::User(UserType::Enum(_))) => {
433 debug!(type_id = %shape.type_identifier, "processing enum");
435 if let Ok(enum_peek) = peek.into_enum()
436 && let Ok(variant) = enum_peek.active_variant()
437 {
438 let tag_str = get_variant_display_name(variant);
439 let fields = &variant.data.fields;
440 debug!(
441 variant_name = tag_str,
442 fields_count = fields.len(),
443 "enum variant"
444 );
445
446 if !fields.is_empty() {
448 if fields.len() == 1
451 && let Ok(Some(inner_value)) = enum_peek.field(0)
452 {
453 let inner_shape = inner_value.shape();
454 if let Type::User(UserType::Struct(s)) = inner_shape.ty
456 && s.kind == StructKind::Struct
457 && let Ok(struct_peek) = inner_value.into_struct()
458 {
459 let mut attrs = Vec::new();
460
461 for (i, field) in s.fields.iter().enumerate() {
462 if let Ok(field_value) = struct_peek.field(i) {
463 if should_skip_falsy(field_value) {
465 continue;
466 }
467 let formatted_value = self.format_peek(field_value);
468 let attr = match change {
469 ElementChange::None => Attr::unchanged(
470 field.name,
471 field.name.len(),
472 formatted_value,
473 ),
474 ElementChange::Deleted => Attr::deleted(
475 field.name,
476 field.name.len(),
477 formatted_value,
478 ),
479 ElementChange::Inserted => Attr::inserted(
480 field.name,
481 field.name.len(),
482 formatted_value,
483 ),
484 ElementChange::MovedFrom | ElementChange::MovedTo => {
485 Attr::unchanged(
486 field.name,
487 field.name.len(),
488 formatted_value,
489 )
490 }
491 };
492 attrs.push(attr);
493 }
494 }
495
496 let changed_groups =
497 group_changed_attrs(&attrs, self.opts.max_line_width, 0);
498
499 return self.tree.new_node(LayoutNode::Element {
500 tag: Cow::Borrowed(tag_str),
501 field_name: None,
502 attrs,
503 changed_groups,
504 change,
505 });
506 }
507 }
508
509 let mut attrs = Vec::new();
511
512 for (i, field) in fields.iter().enumerate() {
513 if let Ok(Some(field_value)) = enum_peek.field(i) {
514 if should_skip_falsy(field_value) {
516 continue;
517 }
518 let formatted_value = self.format_peek(field_value);
519 let attr = match change {
520 ElementChange::None => Attr::unchanged(
521 field.name,
522 field.name.len(),
523 formatted_value,
524 ),
525 ElementChange::Deleted => {
526 Attr::deleted(field.name, field.name.len(), formatted_value)
527 }
528 ElementChange::Inserted => Attr::inserted(
529 field.name,
530 field.name.len(),
531 formatted_value,
532 ),
533 ElementChange::MovedFrom | ElementChange::MovedTo => {
534 Attr::unchanged(
535 field.name,
536 field.name.len(),
537 formatted_value,
538 )
539 }
540 };
541 attrs.push(attr);
542 }
543 }
544
545 let changed_groups =
546 group_changed_attrs(&attrs, self.opts.max_line_width, 0);
547
548 return self.tree.new_node(LayoutNode::Element {
549 tag: Cow::Borrowed(tag_str),
550 field_name: None,
551 attrs,
552 changed_groups,
553 change,
554 });
555 } else {
556 let (span, width) = self.strings.push_str(tag_str);
558 return self.tree.new_node(LayoutNode::Text {
559 value: FormattedValue::new(span, width),
560 change,
561 });
562 }
563 }
564 }
565 _ => {}
566 }
567
568 let formatted = self.format_peek(peek);
570 self.tree.new_node(LayoutNode::Text {
571 value: formatted,
572 change,
573 })
574 }
575
576 #[allow(clippy::too_many_arguments)]
578 fn build_struct<'mem, 'facet>(
579 &mut self,
580 tag: Cow<'static, str>,
581 variant: Option<&'static str>,
582 updates: &std::collections::HashMap<Cow<'static, str>, Diff<'mem, 'facet>>,
583 deletions: &std::collections::HashMap<Cow<'static, str>, Peek<'mem, 'facet>>,
584 insertions: &std::collections::HashMap<Cow<'static, str>, Peek<'mem, 'facet>>,
585 unchanged: &std::collections::HashSet<Cow<'static, str>>,
586 from: Option<Peek<'mem, 'facet>>,
587 to: Option<Peek<'mem, 'facet>>,
588 change: ElementChange,
589 ) -> NodeId {
590 let element_tag = tag;
591
592 if variant.is_some() {
595 }
597
598 let mut attrs = Vec::new();
599 let mut child_nodes = Vec::new();
600
601 debug!(
603 unchanged_count = unchanged.len(),
604 updates_count = updates.len(),
605 deletions_count = deletions.len(),
606 insertions_count = insertions.len(),
607 unchanged_fields = ?unchanged.iter().collect::<Vec<_>>(),
608 updates_fields = ?updates.keys().collect::<Vec<_>>(),
609 "build_struct"
610 );
611 if !unchanged.is_empty() {
612 let unchanged_count = unchanged.len();
613
614 if unchanged_count <= self.opts.max_unchanged_fields {
615 if let Some(from_peek) = from {
617 if let Ok(struct_peek) = from_peek.into_struct() {
618 let mut sorted_unchanged: Vec<_> = unchanged.iter().collect();
619 sorted_unchanged.sort();
620
621 for field_name in sorted_unchanged {
622 if let Ok(field_value) = struct_peek.field_by_name(field_name) {
623 let field_shape = field_value.shape();
624 debug!(
625 field_name = %field_name,
626 field_type = %field_shape.type_identifier,
627 "processing unchanged field"
628 );
629 if should_skip_falsy(field_value) {
631 debug!(field_name = %field_name, "skipping falsy field");
632 continue;
633 }
634 let formatted = self.format_peek(field_value);
635 let name_width = field_name.len();
636 let attr =
637 Attr::unchanged(field_name.clone(), name_width, formatted);
638 attrs.push(attr);
639 }
640 }
641 }
642 } else {
643 }
646 }
647 }
649
650 let mut sorted_updates: Vec<_> = updates.iter().collect();
652 sorted_updates.sort_by(|(a, _), (b, _)| a.cmp(b));
653
654 for (field_name, field_diff) in sorted_updates {
655 let field_from = from.and_then(|p| {
657 p.into_struct()
658 .ok()
659 .and_then(|s| s.field_by_name(field_name).ok())
660 });
661 let field_to = to.and_then(|p| {
662 p.into_struct()
663 .ok()
664 .and_then(|s| s.field_by_name(field_name).ok())
665 });
666
667 match field_diff {
668 Diff::Replace { from, to } => {
669 let from_shape = from.shape();
671 let is_complex = match from_shape.ty {
672 Type::User(UserType::Enum(_)) => true,
673 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => true,
674 _ => false,
675 };
676
677 if is_complex {
678 let from_node = self.build_peek(*from, ElementChange::Deleted);
680 let to_node = self.build_peek(*to, ElementChange::Inserted);
681
682 if let Cow::Borrowed(name) = field_name {
684 if let Some(node) = self.tree.get_mut(from_node)
685 && let LayoutNode::Element { field_name, .. } = node.get_mut()
686 {
687 *field_name = Some(name);
688 }
689 if let Some(node) = self.tree.get_mut(to_node)
690 && let LayoutNode::Element { field_name, .. } = node.get_mut()
691 {
692 *field_name = Some(name);
693 }
694 }
695
696 child_nodes.push(from_node);
697 child_nodes.push(to_node);
698 } else {
699 let old_value = self.format_peek(*from);
701 let new_value = self.format_peek(*to);
702 let name_width = field_name.len();
703 let attr =
704 Attr::changed(field_name.clone(), name_width, old_value, new_value);
705 attrs.push(attr);
706 }
707 }
708 Diff::User {
710 from: shape,
711 value: Value::Tuple { .. },
712 ..
713 } if matches!(shape.def, Def::Option(_)) => {
714 if let (Some(from_peek), Some(to_peek)) = (field_from, field_to) {
716 let inner_from = from_peek.into_option().ok().and_then(|opt| opt.value());
718 let inner_to = to_peek.into_option().ok().and_then(|opt| opt.value());
719
720 if let (Some(from_val), Some(to_val)) = (inner_from, inner_to) {
721 let is_scalar = match from_val.shape().ty {
723 Type::User(UserType::Enum(_)) => false,
724 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => {
725 false
726 }
727 _ => true,
728 };
729
730 if is_scalar {
731 let old_value = self.format_peek(from_val);
733 let new_value = self.format_peek(to_val);
734 let name_width = field_name.len();
735 let attr = Attr::changed(
736 field_name.clone(),
737 name_width,
738 old_value,
739 new_value,
740 );
741 attrs.push(attr);
742 continue;
743 }
744 }
745 }
746
747 let child =
749 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
750 if let Cow::Borrowed(name) = field_name
751 && let Some(node) = self.tree.get_mut(child)
752 {
753 match node.get_mut() {
754 LayoutNode::Element { field_name, .. } => {
755 *field_name = Some(name);
756 }
757 LayoutNode::Sequence { field_name, .. } => {
758 *field_name = Some(name);
759 }
760 _ => {}
761 }
762 }
763 child_nodes.push(child);
764 }
765 Diff::User {
768 from: inner_shape,
769 value:
770 Value::Struct {
771 updates: inner_updates,
772 deletions: inner_deletions,
773 insertions: inner_insertions,
774 unchanged: inner_unchanged,
775 },
776 ..
777 } if inner_updates.len() == 1
778 && inner_deletions.is_empty()
779 && inner_insertions.is_empty()
780 && inner_unchanged.is_empty() =>
781 {
782 let (inner_field_name, inner_field_diff) = inner_updates.iter().next().unwrap();
784
785 if let Diff::Replace {
787 from: inner_from,
788 to: inner_to,
789 } = inner_field_diff
790 {
791 let is_scalar = match inner_from.shape().ty {
793 Type::User(UserType::Enum(_)) => false,
794 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => {
795 false
796 }
797 _ => true,
798 };
799
800 if is_scalar {
801 debug!(
803 field_name = %field_name,
804 inner_type = %inner_shape.type_identifier,
805 inner_field = %inner_field_name,
806 "inlining single-field wrapper as attribute"
807 );
808 let old_value = self.format_peek(*inner_from);
809 let new_value = self.format_peek(*inner_to);
810 let name_width = field_name.len();
811 let attr =
812 Attr::changed(field_name.clone(), name_width, old_value, new_value);
813 attrs.push(attr);
814 continue;
815 }
816 }
817
818 let child =
820 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
821 if let Cow::Borrowed(name) = field_name
822 && let Some(node) = self.tree.get_mut(child)
823 {
824 match node.get_mut() {
825 LayoutNode::Element { field_name, .. } => {
826 *field_name = Some(name);
827 }
828 LayoutNode::Sequence { field_name, .. } => {
829 *field_name = Some(name);
830 }
831 _ => {}
832 }
833 }
834 child_nodes.push(child);
835 }
836 _ => {
837 let child =
839 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
840
841 if let Cow::Borrowed(name) = field_name
844 && let Some(node) = self.tree.get_mut(child)
845 {
846 match node.get_mut() {
847 LayoutNode::Element { field_name, .. } => {
848 *field_name = Some(name);
849 }
850 LayoutNode::Sequence { field_name, .. } => {
851 *field_name = Some(name);
852 }
853 _ => {}
854 }
855 }
856
857 child_nodes.push(child);
858 }
859 }
860 }
861
862 let mut sorted_deletions: Vec<_> = deletions.iter().collect();
864 sorted_deletions.sort_by(|(a, _), (b, _)| a.cmp(b));
865
866 for (field_name, value) in sorted_deletions {
867 let formatted = self.format_peek(*value);
868 let name_width = field_name.len();
869 let attr = Attr::deleted(field_name.clone(), name_width, formatted);
870 attrs.push(attr);
871 }
872
873 let mut sorted_insertions: Vec<_> = insertions.iter().collect();
875 sorted_insertions.sort_by(|(a, _), (b, _)| a.cmp(b));
876
877 for (field_name, value) in sorted_insertions {
878 let formatted = self.format_peek(*value);
879 let name_width = field_name.len();
880 let attr = Attr::inserted(field_name.clone(), name_width, formatted);
881 attrs.push(attr);
882 }
883
884 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
886
887 let node = self.tree.new_node(LayoutNode::Element {
889 tag: element_tag,
890 field_name: None, attrs,
892 changed_groups,
893 change,
894 });
895
896 for child in child_nodes {
898 node.append(child, &mut self.tree);
899 }
900
901 let unchanged_count = unchanged.len();
903 if unchanged_count > self.opts.max_unchanged_fields
904 || (unchanged_count > 0 && from.is_none())
905 {
906 let collapsed = self.tree.new_node(LayoutNode::collapsed(unchanged_count));
907 node.append(collapsed, &mut self.tree);
908 }
909
910 node
911 }
912
913 fn build_tuple<'mem, 'facet>(
915 &mut self,
916 tag: Cow<'static, str>,
917 variant: Option<&'static str>,
918 updates: &Updates<'mem, 'facet>,
919 _from: Option<Peek<'mem, 'facet>>,
920 _to: Option<Peek<'mem, 'facet>>,
921 change: ElementChange,
922 ) -> NodeId {
923 if variant.is_some() {
925 }
927
928 let node = self.tree.new_node(LayoutNode::Element {
930 tag,
931 field_name: None,
932 attrs: Vec::new(),
933 changed_groups: Vec::new(),
934 change,
935 });
936
937 self.build_updates_children(node, updates, "item");
939
940 node
941 }
942
943 fn build_tuple_transparent<'mem, 'facet>(
949 &mut self,
950 updates: &Updates<'mem, 'facet>,
951 _from: Option<Peek<'mem, 'facet>>,
952 _to: Option<Peek<'mem, 'facet>>,
953 change: ElementChange,
954 ) -> NodeId {
955 let temp = self.tree.new_node(LayoutNode::Element {
957 tag: Cow::Borrowed("_transparent"),
958 field_name: None,
959 attrs: Vec::new(),
960 changed_groups: Vec::new(),
961 change,
962 });
963
964 self.build_updates_children(temp, updates, "item");
966
967 let children: Vec<_> = temp.children(&self.tree).collect();
969
970 if children.len() == 1 {
971 let child = children[0];
973 child.detach(&mut self.tree);
974 temp.remove(&mut self.tree);
976 child
977 } else {
978 temp
981 }
982 }
983
984 fn build_enum_tuple_variant<'mem, 'facet>(
992 &mut self,
993 tag: Cow<'static, str>,
994 updates: &Updates<'mem, 'facet>,
995 inner_from: Option<Peek<'mem, 'facet>>,
996 inner_to: Option<Peek<'mem, 'facet>>,
997 change: ElementChange,
998 ) -> NodeId {
999 let interspersed = &updates.0;
1002
1003 if let Some(update_group) = &interspersed.first {
1006 let group_interspersed = &update_group.0;
1007
1008 if let Some(replace_group) = &group_interspersed.first
1010 && replace_group.removals.len() == 1
1011 && replace_group.additions.len() == 1
1012 {
1013 let from = replace_group.removals[0];
1014 let to = replace_group.additions[0];
1015
1016 let mut attrs = Vec::new();
1018
1019 if let (Ok(from_struct), Ok(to_struct)) = (from.into_struct(), to.into_struct())
1020 && let Type::User(UserType::Struct(ty)) = from.shape().ty
1021 {
1022 for (i, field) in ty.fields.iter().enumerate() {
1023 let from_value = from_struct.field(i).ok();
1024 let to_value = to_struct.field(i).ok();
1025
1026 match (from_value, to_value) {
1027 (Some(fv), Some(tv)) => {
1028 let from_formatted = self.format_peek(fv);
1030 let to_formatted = self.format_peek(tv);
1031
1032 if self.strings.get(from_formatted.span)
1033 != self.strings.get(to_formatted.span)
1034 {
1035 attrs.push(Attr::changed(
1037 Cow::Borrowed(field.name),
1038 field.name.len(),
1039 from_formatted,
1040 to_formatted,
1041 ));
1042 } else {
1043 if !should_skip_falsy(fv) {
1045 attrs.push(Attr::unchanged(
1046 Cow::Borrowed(field.name),
1047 field.name.len(),
1048 from_formatted,
1049 ));
1050 }
1051 }
1052 }
1053 (Some(fv), None) => {
1054 if !should_skip_falsy(fv) {
1056 let formatted = self.format_peek(fv);
1057 attrs.push(Attr::deleted(
1058 Cow::Borrowed(field.name),
1059 field.name.len(),
1060 formatted,
1061 ));
1062 }
1063 }
1064 (None, Some(tv)) => {
1065 if !should_skip_falsy(tv) {
1067 let formatted = self.format_peek(tv);
1068 attrs.push(Attr::inserted(
1069 Cow::Borrowed(field.name),
1070 field.name.len(),
1071 formatted,
1072 ));
1073 }
1074 }
1075 (None, None) => {
1076 }
1078 }
1079 }
1080 }
1081
1082 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
1083
1084 return self.tree.new_node(LayoutNode::Element {
1085 tag,
1086 field_name: None,
1087 attrs,
1088 changed_groups,
1089 change,
1090 });
1091 }
1092 }
1093
1094 let single_diff = {
1096 let mut found_diff: Option<&Diff<'mem, 'facet>> = None;
1097
1098 if let Some(update_group) = &interspersed.first {
1100 let group_interspersed = &update_group.0;
1101
1102 if let Some(diffs) = &group_interspersed.last
1104 && diffs.len() == 1
1105 && found_diff.is_none()
1106 {
1107 found_diff = Some(&diffs[0]);
1108 }
1109 for (diffs, _replace) in &group_interspersed.values {
1110 if diffs.len() == 1 && found_diff.is_none() {
1111 found_diff = Some(&diffs[0]);
1112 }
1113 }
1114 }
1115
1116 found_diff
1117 };
1118
1119 if let Some(diff) = single_diff {
1121 match diff {
1122 Diff::User {
1123 value:
1124 Value::Struct {
1125 updates,
1126 deletions,
1127 insertions,
1128 unchanged,
1129 },
1130 ..
1131 } => {
1132 return self.build_struct(
1134 tag.clone(),
1135 None,
1136 updates,
1137 deletions,
1138 insertions,
1139 unchanged,
1140 inner_from,
1141 inner_to,
1142 change,
1143 );
1144 }
1145 Diff::Replace { from, to } => {
1146 let mut attrs = Vec::new();
1149
1150 if let Ok(struct_peek) = from.into_struct()
1152 && let Type::User(UserType::Struct(ty)) = from.shape().ty
1153 {
1154 for (i, field) in ty.fields.iter().enumerate() {
1155 if let Ok(field_value) = struct_peek.field(i) {
1156 if should_skip_falsy(field_value) {
1157 continue;
1158 }
1159 let formatted = self.format_peek(field_value);
1160 attrs.push(Attr::deleted(
1161 Cow::Borrowed(field.name),
1162 field.name.len(),
1163 formatted,
1164 ));
1165 }
1166 }
1167 }
1168
1169 if let Ok(struct_peek) = to.into_struct()
1171 && let Type::User(UserType::Struct(ty)) = to.shape().ty
1172 {
1173 for (i, field) in ty.fields.iter().enumerate() {
1174 if let Ok(field_value) = struct_peek.field(i) {
1175 if should_skip_falsy(field_value) {
1176 continue;
1177 }
1178 let formatted = self.format_peek(field_value);
1179 attrs.push(Attr::inserted(
1180 Cow::Borrowed(field.name),
1181 field.name.len(),
1182 formatted,
1183 ));
1184 }
1185 }
1186 }
1187
1188 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
1189
1190 return self.tree.new_node(LayoutNode::Element {
1191 tag: tag.clone(),
1192 field_name: None,
1193 attrs,
1194 changed_groups,
1195 change,
1196 });
1197 }
1198 _ => {}
1199 }
1200 }
1201
1202 let node = self.tree.new_node(LayoutNode::Element {
1204 tag,
1205 field_name: None,
1206 attrs: Vec::new(),
1207 changed_groups: Vec::new(),
1208 change,
1209 });
1210
1211 self.build_updates_children(node, updates, "item");
1213
1214 node
1215 }
1216
1217 fn build_sequence(
1219 &mut self,
1220 updates: &Updates<'_, '_>,
1221 change: ElementChange,
1222 item_type: &'static str,
1223 ) -> NodeId {
1224 let node = self.tree.new_node(LayoutNode::Sequence {
1226 change,
1227 item_type,
1228 field_name: None,
1229 });
1230
1231 self.build_updates_children(node, updates, item_type);
1233
1234 node
1235 }
1236
1237 fn build_updates_children(
1243 &mut self,
1244 parent: NodeId,
1245 updates: &Updates<'_, '_>,
1246 _item_type: &'static str,
1247 ) {
1248 let mut items: Vec<(Peek<'_, '_>, ElementChange)> = Vec::new();
1250 let mut nested_diffs: Vec<&Diff<'_, '_>> = Vec::new();
1251
1252 let interspersed = &updates.0;
1253
1254 if let Some(update_group) = &interspersed.first {
1256 self.collect_updates_group_items(&mut items, &mut nested_diffs, update_group);
1257 }
1258
1259 for (unchanged_items, update_group) in &interspersed.values {
1261 for item in unchanged_items {
1263 items.push((*item, ElementChange::None));
1264 }
1265
1266 self.collect_updates_group_items(&mut items, &mut nested_diffs, update_group);
1267 }
1268
1269 if let Some(unchanged_items) = &interspersed.last {
1271 for item in unchanged_items {
1272 items.push((*item, ElementChange::None));
1273 }
1274 }
1275
1276 tracing::debug!(
1277 items_count = items.len(),
1278 nested_diffs_count = nested_diffs.len(),
1279 "collected sequence items"
1280 );
1281
1282 for diff in nested_diffs {
1284 debug!(diff_type = ?std::mem::discriminant(diff), "building nested diff");
1285 let (from_peek, to_peek) = match diff {
1287 Diff::User { .. } => {
1288 (None, None)
1292 }
1293 Diff::Replace { from, to } => (Some(*from), Some(*to)),
1294 _ => (None, None),
1295 };
1296 let child = self.build_diff(diff, from_peek, to_peek, ElementChange::None);
1297 parent.append(child, &mut self.tree);
1298 }
1299
1300 for (item_peek, item_change) in items {
1302 let child = self.build_peek(item_peek, item_change);
1303 parent.append(child, &mut self.tree);
1304 }
1305 }
1306
1307 fn collect_updates_group_items<'a, 'mem: 'a, 'facet: 'a>(
1310 &self,
1311 items: &mut Vec<(Peek<'mem, 'facet>, ElementChange)>,
1312 nested_diffs: &mut Vec<&'a Diff<'mem, 'facet>>,
1313 group: &'a UpdatesGroup<'mem, 'facet>,
1314 ) {
1315 let interspersed = &group.0;
1316
1317 if let Some(replace) = &interspersed.first {
1319 self.collect_replace_group_items(items, replace);
1320 }
1321
1322 for (diffs, replace) in &interspersed.values {
1324 for diff in diffs {
1326 nested_diffs.push(diff);
1327 }
1328 self.collect_replace_group_items(items, replace);
1329 }
1330
1331 if let Some(diffs) = &interspersed.last {
1333 for diff in diffs {
1334 nested_diffs.push(diff);
1335 }
1336 }
1337 }
1338
1339 fn collect_replace_group_items<'a, 'mem: 'a, 'facet: 'a>(
1341 &self,
1342 items: &mut Vec<(Peek<'mem, 'facet>, ElementChange)>,
1343 group: &'a ReplaceGroup<'mem, 'facet>,
1344 ) {
1345 for removal in &group.removals {
1347 items.push((*removal, ElementChange::Deleted));
1348 }
1349
1350 for addition in &group.additions {
1352 items.push((*addition, ElementChange::Inserted));
1353 }
1354 }
1355
1356 fn format_peek(&mut self, peek: Peek<'_, '_>) -> FormattedValue {
1358 let shape = peek.shape();
1359 debug!(
1360 type_id = %shape.type_identifier,
1361 def = ?shape.def,
1362 "format_peek"
1363 );
1364
1365 if let Def::Option(_) = shape.def
1367 && let Ok(opt) = peek.into_option()
1368 {
1369 if let Some(inner) = opt.value() {
1370 return self.format_peek(inner);
1371 }
1372 let (span, width) = self.strings.push_str("null");
1374 return FormattedValue::with_type(span, width, ValueType::Null);
1375 }
1376
1377 if let Some(precision) = self.opts.float_precision
1379 && let Type::Primitive(PrimitiveType::Numeric(NumericType::Float)) = shape.ty
1380 {
1381 if let Ok(v) = peek.get::<f64>() {
1383 let formatted = format!("{:.prec$}", v, prec = precision);
1384 let formatted = formatted.trim_end_matches('0').trim_end_matches('.');
1386 let (span, width) = self.strings.push_str(formatted);
1387 return FormattedValue::with_type(span, width, ValueType::Number);
1388 }
1389 if let Ok(v) = peek.get::<f32>() {
1390 let formatted = format!("{:.prec$}", v, prec = precision);
1391 let formatted = formatted.trim_end_matches('0').trim_end_matches('.');
1392 let (span, width) = self.strings.push_str(formatted);
1393 return FormattedValue::with_type(span, width, ValueType::Number);
1394 }
1395 }
1396
1397 let (span, width) = self.strings.format(|w| self.flavor.format_value(peek, w));
1398 let value_type = determine_value_type(peek);
1399 FormattedValue::with_type(span, width, value_type)
1400 }
1401
1402 fn finish(self, root: NodeId) -> Layout {
1404 Layout {
1405 strings: self.strings,
1406 tree: self.tree,
1407 root,
1408 }
1409 }
1410}
1411
1412#[cfg(test)]
1413mod tests {
1414 use super::*;
1415 use crate::layout::render::{RenderOptions, render_to_string};
1416 use crate::layout::{RustFlavor, XmlFlavor};
1417
1418 #[test]
1419 fn test_build_equal_diff() {
1420 let value = 42i32;
1421 let peek = Peek::new(&value);
1422 let diff = Diff::Equal { value: Some(peek) };
1423
1424 let layout = build_layout(&diff, peek, peek, &BuildOptions::default(), &RustFlavor);
1425
1426 let root = layout.get(layout.root).unwrap();
1428 assert!(matches!(root, LayoutNode::Text { .. }));
1429 }
1430
1431 #[test]
1432 fn test_build_replace_diff() {
1433 let from = 10i32;
1434 let to = 20i32;
1435 let diff = Diff::Replace {
1436 from: Peek::new(&from),
1437 to: Peek::new(&to),
1438 };
1439
1440 let layout = build_layout(
1441 &diff,
1442 Peek::new(&from),
1443 Peek::new(&to),
1444 &BuildOptions::default(),
1445 &RustFlavor,
1446 );
1447
1448 let root = layout.get(layout.root).unwrap();
1450 match root {
1451 LayoutNode::Element { tag, .. } => assert_eq!(tag.as_ref(), "_replace"),
1452 _ => panic!("expected Element node"),
1453 }
1454
1455 let children: Vec<_> = layout.children(layout.root).collect();
1456 assert_eq!(children.len(), 2);
1457 }
1458
1459 #[test]
1460 fn test_build_and_render_replace() {
1461 let from = 10i32;
1462 let to = 20i32;
1463 let diff = Diff::Replace {
1464 from: Peek::new(&from),
1465 to: Peek::new(&to),
1466 };
1467
1468 let layout = build_layout(
1469 &diff,
1470 Peek::new(&from),
1471 Peek::new(&to),
1472 &BuildOptions::default(),
1473 &RustFlavor,
1474 );
1475 let output = render_to_string(&layout, &RenderOptions::plain(), &XmlFlavor);
1476
1477 assert!(
1479 output.contains("10"),
1480 "output should contain old value: {}",
1481 output
1482 );
1483 assert!(
1484 output.contains("20"),
1485 "output should contain new value: {}",
1486 output
1487 );
1488 }
1489
1490 #[test]
1491 fn test_build_options_default() {
1492 let opts = BuildOptions::default();
1493 assert_eq!(opts.max_line_width, 80);
1494 assert_eq!(opts.max_unchanged_fields, 5);
1495 assert_eq!(opts.collapse_threshold, 3);
1496 }
1497}