1use std::borrow::Cow;
15
16#[cfg(feature = "tracing")]
17use tracing::debug;
18
19#[cfg(not(feature = "tracing"))]
20macro_rules! debug {
21 ($($arg:tt)*) => {};
22}
23
24use facet_core::{Def, NumericType, PrimitiveType, Shape, StructKind, TextualType, Type, UserType};
25use facet_reflect::Peek;
26use indextree::{Arena, NodeId};
27
28use super::{
29 Attr, DiffFlavor, ElementChange, FormatArena, FormattedValue, Layout, LayoutNode, ValueType,
30 group_changed_attrs,
31};
32use crate::{Diff, ReplaceGroup, Updates, UpdatesGroup, Value};
33
34fn get_shape_display_name(shape: &Shape) -> &'static str {
36 if let Some(renamed) = shape.get_builtin_attr_value::<&str>("rename") {
37 return renamed;
38 }
39 shape.type_identifier
40}
41
42fn shape_has_xml_attrs(shape: &Shape) -> bool {
46 shape.attributes.iter().any(|attr| attr.ns == Some("xml"))
47}
48
49fn get_xml_display_name(shape: &Shape) -> Cow<'static, str> {
54 let base_name = get_shape_display_name(shape);
55
56 if let Type::User(UserType::Struct(_)) = shape.ty
58 && !shape_has_xml_attrs(shape)
59 {
60 return Cow::Owned(format!("@{}", base_name));
61 }
62
63 Cow::Borrowed(base_name)
64}
65
66fn get_variant_display_name(variant: &facet_core::Variant) -> &'static str {
68 if let Some(attr) = variant.get_builtin_attr("rename")
69 && let Some(renamed) = attr.get_as::<&'static str>()
70 {
71 return renamed;
72 }
73 variant.name
74}
75
76fn should_skip_falsy(peek: Peek<'_, '_>) -> bool {
81 let shape = peek.shape();
82 match shape.def {
83 Def::Option(_) => {
85 if let Ok(opt) = peek.into_option() {
86 return opt.is_none();
87 }
88 }
89 Def::List(_) => {
91 if let Ok(list) = peek.into_list() {
92 return list.is_empty();
93 }
94 }
95 Def::Map(_) => {
97 if let Ok(map) = peek.into_map() {
98 return map.is_empty();
99 }
100 }
101 _ => {}
102 }
103 false
104}
105
106fn determine_value_type(peek: Peek<'_, '_>) -> ValueType {
108 let shape = peek.shape();
109
110 if let Def::Option(_) = shape.def {
112 if let Ok(opt) = peek.into_option() {
114 if opt.is_none() {
115 return ValueType::Null;
116 }
117 if let Some(inner) = opt.value() {
119 return determine_value_type(inner);
120 }
121 }
122 return ValueType::Other;
123 }
124
125 match shape.ty {
127 Type::Primitive(p) => match p {
128 PrimitiveType::Boolean => ValueType::Boolean,
129 PrimitiveType::Numeric(NumericType::Integer { .. })
130 | PrimitiveType::Numeric(NumericType::Float) => ValueType::Number,
131 PrimitiveType::Textual(TextualType::Char)
132 | PrimitiveType::Textual(TextualType::Str) => ValueType::String,
133 PrimitiveType::Never => ValueType::Null,
134 },
135 _ => ValueType::Other,
136 }
137}
138
139#[derive(Clone, Debug)]
141pub struct BuildOptions {
142 pub max_line_width: usize,
144 pub max_unchanged_fields: usize,
147 pub collapse_threshold: usize,
149 pub float_precision: Option<usize>,
153}
154
155impl Default for BuildOptions {
156 fn default() -> Self {
157 Self {
158 max_line_width: 80,
159 max_unchanged_fields: 5,
160 collapse_threshold: 3,
161 float_precision: None,
162 }
163 }
164}
165
166impl BuildOptions {
167 pub const fn with_float_precision(mut self, precision: usize) -> Self {
173 self.float_precision = Some(precision);
174 self
175 }
176}
177
178pub fn build_layout<'mem, 'facet, F: DiffFlavor>(
190 diff: &Diff<'mem, 'facet>,
191 from: Peek<'mem, 'facet>,
192 to: Peek<'mem, 'facet>,
193 opts: &BuildOptions,
194 flavor: &F,
195) -> Layout {
196 let mut builder = LayoutBuilder::new(opts.clone(), flavor);
197 let root_id = builder.build(diff, Some(from), Some(to));
198 builder.finish(root_id)
199}
200
201struct LayoutBuilder<'f, F: DiffFlavor> {
203 strings: FormatArena,
205 tree: Arena<LayoutNode>,
207 opts: BuildOptions,
209 flavor: &'f F,
211}
212
213impl<'f, F: DiffFlavor> LayoutBuilder<'f, F> {
214 fn new(opts: BuildOptions, flavor: &'f F) -> Self {
215 Self {
216 strings: FormatArena::new(),
217 tree: Arena::new(),
218 opts,
219 flavor,
220 }
221 }
222
223 fn build<'mem, 'facet>(
225 &mut self,
226 diff: &Diff<'mem, 'facet>,
227 from: Option<Peek<'mem, 'facet>>,
228 to: Option<Peek<'mem, 'facet>>,
229 ) -> NodeId {
230 self.build_diff(diff, from, to, ElementChange::None)
231 }
232
233 fn build_diff<'mem, 'facet>(
235 &mut self,
236 diff: &Diff<'mem, 'facet>,
237 from: Option<Peek<'mem, 'facet>>,
238 to: Option<Peek<'mem, 'facet>>,
239 change: ElementChange,
240 ) -> NodeId {
241 match diff {
242 Diff::Equal { value } => {
243 if let Some(peek) = value {
245 self.build_peek(*peek, ElementChange::None)
246 } else {
247 let (span, width) = self.strings.push_str("(equal)");
249 let value = FormattedValue::new(span, width);
250 self.tree.new_node(LayoutNode::Text {
251 value,
252 change: ElementChange::None,
253 })
254 }
255 }
256 Diff::Replace { from, to } => {
257 let root = self.tree.new_node(LayoutNode::element("_replace"));
259
260 let from_node = self.build_peek(*from, ElementChange::Deleted);
261 let to_node = self.build_peek(*to, ElementChange::Inserted);
262
263 root.append(from_node, &mut self.tree);
264 root.append(to_node, &mut self.tree);
265
266 root
267 }
268 Diff::User {
269 from: from_shape,
270 to: _to_shape,
271 variant,
272 value,
273 } => {
274 if matches!(from_shape.def, Def::Option(_))
277 && let Value::Tuple { updates } = value
278 {
279 let inner_from =
281 from.and_then(|p| p.into_option().ok().and_then(|opt| opt.value()));
282 let inner_to =
283 to.and_then(|p| p.into_option().ok().and_then(|opt| opt.value()));
284
285 return self.build_tuple_transparent(updates, inner_from, inner_to, change);
288 }
289
290 if let Some(variant_name) = *variant
293 && let Type::User(UserType::Enum(enum_ty)) = from_shape.ty
294 {
295 let tag =
297 if let Some(v) = enum_ty.variants.iter().find(|v| v.name == variant_name) {
298 Cow::Borrowed(get_variant_display_name(v))
299 } else {
300 Cow::Borrowed(variant_name)
301 };
302 debug!(
303 tag = tag.as_ref(),
304 variant_name, "Diff::User enum variant - using variant tag"
305 );
306
307 if let Value::Tuple { updates } = value {
309 let inner_from = from.and_then(|p| {
311 p.into_enum().ok().and_then(|e| e.field(0).ok().flatten())
312 });
313 let inner_to = to.and_then(|p| {
314 p.into_enum().ok().and_then(|e| e.field(0).ok().flatten())
315 });
316
317 return self
319 .build_enum_tuple_variant(tag, updates, inner_from, inner_to, change);
320 }
321
322 if let Value::Struct {
324 updates,
325 deletions,
326 insertions,
327 unchanged,
328 } = value
329 {
330 return self.build_struct(
331 tag, None, updates, deletions, insertions, unchanged, from, to, change,
332 );
333 }
334 }
335
336 let tag = get_xml_display_name(from_shape);
339 debug!(tag = tag.as_ref(), variant = ?variant, value_type = ?std::mem::discriminant(value), "Diff::User");
340
341 match value {
342 Value::Struct {
343 updates,
344 deletions,
345 insertions,
346 unchanged,
347 } => self.build_struct(
348 tag, *variant, updates, deletions, insertions, unchanged, from, to, change,
349 ),
350 Value::Tuple { updates } => {
351 debug!(tag = tag.as_ref(), "Value::Tuple - building tuple");
352 self.build_tuple(tag, *variant, updates, from, to, change)
353 }
354 }
355 }
356 Diff::Sequence {
357 from: _seq_shape_from,
358 to: _seq_shape_to,
359 updates,
360 } => {
361 let item_type = from
363 .and_then(|p| p.into_list_like().ok())
364 .and_then(|list| list.iter().next())
365 .or_else(|| {
366 to.and_then(|p| p.into_list_like().ok())
367 .and_then(|list| list.iter().next())
368 })
369 .map(|item| get_shape_display_name(item.shape()))
370 .unwrap_or("item");
371 self.build_sequence(updates, change, item_type)
372 }
373 }
374 }
375
376 fn build_peek(&mut self, peek: Peek<'_, '_>, change: ElementChange) -> NodeId {
378 let shape = peek.shape();
379 debug!(
380 type_id = %shape.type_identifier,
381 def = ?shape.def,
382 change = ?change,
383 "build_peek"
384 );
385
386 match (shape.def, shape.ty) {
388 (Def::Option(_), _) => {
390 if let Ok(opt) = peek.into_option()
391 && let Some(inner) = opt.value()
392 {
393 return self.build_peek(inner, change);
395 }
396 let (span, width) = self.strings.push_str("null");
398 return self.tree.new_node(LayoutNode::Text {
399 value: FormattedValue::with_type(span, width, ValueType::Null),
400 change,
401 });
402 }
403 (_, Type::User(UserType::Struct(ty))) if ty.kind == StructKind::Struct => {
404 if let Ok(struct_peek) = peek.into_struct() {
406 let tag = get_xml_display_name(shape);
407 let mut attrs = Vec::new();
408
409 for (i, field) in ty.fields.iter().enumerate() {
410 if let Ok(field_value) = struct_peek.field(i) {
411 if should_skip_falsy(field_value) {
413 continue;
414 }
415 let formatted_value = self.format_peek(field_value);
416 let attr = match change {
417 ElementChange::None => {
418 Attr::unchanged(field.name, field.name.len(), formatted_value)
419 }
420 ElementChange::Deleted => {
421 Attr::deleted(field.name, field.name.len(), formatted_value)
422 }
423 ElementChange::Inserted => {
424 Attr::inserted(field.name, field.name.len(), formatted_value)
425 }
426 ElementChange::MovedFrom | ElementChange::MovedTo => {
427 Attr::unchanged(field.name, field.name.len(), formatted_value)
429 }
430 };
431 attrs.push(attr);
432 }
433 }
434
435 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
436
437 return self.tree.new_node(LayoutNode::Element {
438 tag,
439 field_name: None,
440 attrs,
441 changed_groups,
442 change,
443 });
444 }
445 }
446 (_, Type::User(UserType::Enum(_))) => {
447 debug!(type_id = %shape.type_identifier, "processing enum");
449 if let Ok(enum_peek) = peek.into_enum()
450 && let Ok(variant) = enum_peek.active_variant()
451 {
452 let tag_str = get_variant_display_name(variant);
453 let fields = &variant.data.fields;
454 debug!(
455 variant_name = tag_str,
456 fields_count = fields.len(),
457 "enum variant"
458 );
459
460 if !fields.is_empty() {
462 if fields.len() == 1
465 && let Ok(Some(inner_value)) = enum_peek.field(0)
466 {
467 let inner_shape = inner_value.shape();
468 if let Type::User(UserType::Struct(s)) = inner_shape.ty
470 && s.kind == StructKind::Struct
471 && let Ok(struct_peek) = inner_value.into_struct()
472 {
473 let mut attrs = Vec::new();
474
475 for (i, field) in s.fields.iter().enumerate() {
476 if let Ok(field_value) = struct_peek.field(i) {
477 if should_skip_falsy(field_value) {
479 continue;
480 }
481 let formatted_value = self.format_peek(field_value);
482 let attr = match change {
483 ElementChange::None => Attr::unchanged(
484 field.name,
485 field.name.len(),
486 formatted_value,
487 ),
488 ElementChange::Deleted => Attr::deleted(
489 field.name,
490 field.name.len(),
491 formatted_value,
492 ),
493 ElementChange::Inserted => Attr::inserted(
494 field.name,
495 field.name.len(),
496 formatted_value,
497 ),
498 ElementChange::MovedFrom | ElementChange::MovedTo => {
499 Attr::unchanged(
500 field.name,
501 field.name.len(),
502 formatted_value,
503 )
504 }
505 };
506 attrs.push(attr);
507 }
508 }
509
510 let changed_groups =
511 group_changed_attrs(&attrs, self.opts.max_line_width, 0);
512
513 return self.tree.new_node(LayoutNode::Element {
514 tag: Cow::Borrowed(tag_str),
515 field_name: None,
516 attrs,
517 changed_groups,
518 change,
519 });
520 }
521 }
522
523 let mut attrs = Vec::new();
525
526 for (i, field) in fields.iter().enumerate() {
527 if let Ok(Some(field_value)) = enum_peek.field(i) {
528 if should_skip_falsy(field_value) {
530 continue;
531 }
532 let formatted_value = self.format_peek(field_value);
533 let attr = match change {
534 ElementChange::None => Attr::unchanged(
535 field.name,
536 field.name.len(),
537 formatted_value,
538 ),
539 ElementChange::Deleted => {
540 Attr::deleted(field.name, field.name.len(), formatted_value)
541 }
542 ElementChange::Inserted => Attr::inserted(
543 field.name,
544 field.name.len(),
545 formatted_value,
546 ),
547 ElementChange::MovedFrom | ElementChange::MovedTo => {
548 Attr::unchanged(
549 field.name,
550 field.name.len(),
551 formatted_value,
552 )
553 }
554 };
555 attrs.push(attr);
556 }
557 }
558
559 let changed_groups =
560 group_changed_attrs(&attrs, self.opts.max_line_width, 0);
561
562 return self.tree.new_node(LayoutNode::Element {
563 tag: Cow::Borrowed(tag_str),
564 field_name: None,
565 attrs,
566 changed_groups,
567 change,
568 });
569 } else {
570 let (span, width) = self.strings.push_str(tag_str);
572 return self.tree.new_node(LayoutNode::Text {
573 value: FormattedValue::new(span, width),
574 change,
575 });
576 }
577 }
578 }
579 _ => {}
580 }
581
582 let formatted = self.format_peek(peek);
584 self.tree.new_node(LayoutNode::Text {
585 value: formatted,
586 change,
587 })
588 }
589
590 #[allow(clippy::too_many_arguments)]
592 fn build_struct<'mem, 'facet>(
593 &mut self,
594 tag: Cow<'static, str>,
595 variant: Option<&'static str>,
596 updates: &std::collections::HashMap<Cow<'static, str>, Diff<'mem, 'facet>>,
597 deletions: &std::collections::HashMap<Cow<'static, str>, Peek<'mem, 'facet>>,
598 insertions: &std::collections::HashMap<Cow<'static, str>, Peek<'mem, 'facet>>,
599 unchanged: &std::collections::HashSet<Cow<'static, str>>,
600 from: Option<Peek<'mem, 'facet>>,
601 to: Option<Peek<'mem, 'facet>>,
602 change: ElementChange,
603 ) -> NodeId {
604 let element_tag = tag;
605
606 if variant.is_some() {
609 }
611
612 let mut attrs = Vec::new();
613 let mut child_nodes = Vec::new();
614
615 debug!(
617 unchanged_count = unchanged.len(),
618 updates_count = updates.len(),
619 deletions_count = deletions.len(),
620 insertions_count = insertions.len(),
621 unchanged_fields = ?unchanged.iter().collect::<Vec<_>>(),
622 updates_fields = ?updates.keys().collect::<Vec<_>>(),
623 "build_struct"
624 );
625 if !unchanged.is_empty() {
626 let unchanged_count = unchanged.len();
627
628 if unchanged_count <= self.opts.max_unchanged_fields {
629 if let Some(from_peek) = from {
631 if let Ok(struct_peek) = from_peek.into_struct() {
632 let mut sorted_unchanged: Vec<_> = unchanged.iter().collect();
633 sorted_unchanged.sort();
634
635 for field_name in sorted_unchanged {
636 if let Ok(field_value) = struct_peek.field_by_name(field_name) {
637 debug!(
638 field_name = %field_name,
639 field_type = %field_value.shape().type_identifier,
640 "processing unchanged field"
641 );
642 if should_skip_falsy(field_value) {
644 debug!(field_name = %field_name, "skipping falsy field");
645 continue;
646 }
647 let formatted = self.format_peek(field_value);
648 let name_width = field_name.len();
649 let attr =
650 Attr::unchanged(field_name.clone(), name_width, formatted);
651 attrs.push(attr);
652 }
653 }
654 }
655 } else {
656 }
659 }
660 }
662
663 let mut sorted_updates: Vec<_> = updates.iter().collect();
665 sorted_updates.sort_by_key(|(a, _)| *a);
666
667 for (field_name, field_diff) in sorted_updates {
668 let field_from = from.and_then(|p| {
670 p.into_struct()
671 .ok()
672 .and_then(|s| s.field_by_name(field_name).ok())
673 });
674 let field_to = to.and_then(|p| {
675 p.into_struct()
676 .ok()
677 .and_then(|s| s.field_by_name(field_name).ok())
678 });
679
680 match field_diff {
681 Diff::Replace { from, to } => {
682 let from_shape = from.shape();
684 let is_complex = match from_shape.ty {
685 Type::User(UserType::Enum(_)) => true,
686 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => true,
687 _ => false,
688 };
689
690 if is_complex {
691 let from_node = self.build_peek(*from, ElementChange::Deleted);
693 let to_node = self.build_peek(*to, ElementChange::Inserted);
694
695 if let Cow::Borrowed(name) = field_name {
697 if let Some(node) = self.tree.get_mut(from_node)
698 && let LayoutNode::Element { field_name, .. } = node.get_mut()
699 {
700 *field_name = Some(name);
701 }
702 if let Some(node) = self.tree.get_mut(to_node)
703 && let LayoutNode::Element { field_name, .. } = node.get_mut()
704 {
705 *field_name = Some(name);
706 }
707 }
708
709 child_nodes.push(from_node);
710 child_nodes.push(to_node);
711 } else {
712 let old_value = self.format_peek(*from);
714 let new_value = self.format_peek(*to);
715 let name_width = field_name.len();
716 let attr =
717 Attr::changed(field_name.clone(), name_width, old_value, new_value);
718 attrs.push(attr);
719 }
720 }
721 Diff::User {
723 from: shape,
724 value: Value::Tuple { .. },
725 ..
726 } if matches!(shape.def, Def::Option(_)) => {
727 if let (Some(from_peek), Some(to_peek)) = (field_from, field_to) {
729 let inner_from = from_peek.into_option().ok().and_then(|opt| opt.value());
731 let inner_to = to_peek.into_option().ok().and_then(|opt| opt.value());
732
733 if let (Some(from_val), Some(to_val)) = (inner_from, inner_to) {
734 let is_scalar = match from_val.shape().ty {
736 Type::User(UserType::Enum(_)) => false,
737 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => {
738 false
739 }
740 _ => true,
741 };
742
743 if is_scalar {
744 let old_value = self.format_peek(from_val);
746 let new_value = self.format_peek(to_val);
747 let name_width = field_name.len();
748 let attr = Attr::changed(
749 field_name.clone(),
750 name_width,
751 old_value,
752 new_value,
753 );
754 attrs.push(attr);
755 continue;
756 }
757 }
758 }
759
760 let child =
762 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
763 if let Cow::Borrowed(name) = field_name
764 && let Some(node) = self.tree.get_mut(child)
765 {
766 match node.get_mut() {
767 LayoutNode::Element { field_name, .. } => {
768 *field_name = Some(name);
769 }
770 LayoutNode::Sequence { field_name, .. } => {
771 *field_name = Some(name);
772 }
773 _ => {}
774 }
775 }
776 child_nodes.push(child);
777 }
778 Diff::User {
781 from: inner_shape,
782 value:
783 Value::Struct {
784 updates: inner_updates,
785 deletions: inner_deletions,
786 insertions: inner_insertions,
787 unchanged: inner_unchanged,
788 },
789 ..
790 } if inner_updates.len() == 1
791 && inner_deletions.is_empty()
792 && inner_insertions.is_empty()
793 && inner_unchanged.is_empty() =>
794 {
795 let (inner_field_name, inner_field_diff) = inner_updates.iter().next().unwrap();
797
798 if let Diff::Replace {
800 from: inner_from,
801 to: inner_to,
802 } = inner_field_diff
803 {
804 let is_scalar = match inner_from.shape().ty {
806 Type::User(UserType::Enum(_)) => false,
807 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => {
808 false
809 }
810 _ => true,
811 };
812
813 if is_scalar {
814 let _ = inner_field_name; debug!(
817 field_name = %field_name,
818 inner_type = %inner_shape.type_identifier,
819 inner_field = %inner_field_name,
820 "inlining single-field wrapper as attribute"
821 );
822 let old_value = self.format_peek(*inner_from);
823 let new_value = self.format_peek(*inner_to);
824 let name_width = field_name.len();
825 let attr =
826 Attr::changed(field_name.clone(), name_width, old_value, new_value);
827 attrs.push(attr);
828 continue;
829 }
830 }
831
832 let child =
834 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
835 if let Cow::Borrowed(name) = field_name
836 && let Some(node) = self.tree.get_mut(child)
837 {
838 match node.get_mut() {
839 LayoutNode::Element { field_name, .. } => {
840 *field_name = Some(name);
841 }
842 LayoutNode::Sequence { field_name, .. } => {
843 *field_name = Some(name);
844 }
845 _ => {}
846 }
847 }
848 child_nodes.push(child);
849 }
850 _ => {
851 let child =
853 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
854
855 if let Cow::Borrowed(name) = field_name
858 && let Some(node) = self.tree.get_mut(child)
859 {
860 match node.get_mut() {
861 LayoutNode::Element { field_name, .. } => {
862 *field_name = Some(name);
863 }
864 LayoutNode::Sequence { field_name, .. } => {
865 *field_name = Some(name);
866 }
867 _ => {}
868 }
869 }
870
871 child_nodes.push(child);
872 }
873 }
874 }
875
876 let mut sorted_deletions: Vec<_> = deletions.iter().collect();
878 sorted_deletions.sort_by_key(|(a, _)| *a);
879
880 for (field_name, value) in sorted_deletions {
881 let formatted = self.format_peek(*value);
882 let name_width = field_name.len();
883 let attr = Attr::deleted(field_name.clone(), name_width, formatted);
884 attrs.push(attr);
885 }
886
887 let mut sorted_insertions: Vec<_> = insertions.iter().collect();
889 sorted_insertions.sort_by_key(|(a, _)| *a);
890
891 for (field_name, value) in sorted_insertions {
892 let formatted = self.format_peek(*value);
893 let name_width = field_name.len();
894 let attr = Attr::inserted(field_name.clone(), name_width, formatted);
895 attrs.push(attr);
896 }
897
898 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
900
901 let node = self.tree.new_node(LayoutNode::Element {
903 tag: element_tag,
904 field_name: None, attrs,
906 changed_groups,
907 change,
908 });
909
910 for child in child_nodes {
912 node.append(child, &mut self.tree);
913 }
914
915 let unchanged_count = unchanged.len();
917 if unchanged_count > self.opts.max_unchanged_fields
918 || (unchanged_count > 0 && from.is_none())
919 {
920 let collapsed = self.tree.new_node(LayoutNode::collapsed(unchanged_count));
921 node.append(collapsed, &mut self.tree);
922 }
923
924 node
925 }
926
927 fn build_tuple<'mem, 'facet>(
929 &mut self,
930 tag: Cow<'static, str>,
931 variant: Option<&'static str>,
932 updates: &Updates<'mem, 'facet>,
933 _from: Option<Peek<'mem, 'facet>>,
934 _to: Option<Peek<'mem, 'facet>>,
935 change: ElementChange,
936 ) -> NodeId {
937 if variant.is_some() {
939 }
941
942 let node = self.tree.new_node(LayoutNode::Element {
944 tag,
945 field_name: None,
946 attrs: Vec::new(),
947 changed_groups: Vec::new(),
948 change,
949 });
950
951 self.build_updates_children(node, updates, "item");
953
954 node
955 }
956
957 fn build_tuple_transparent<'mem, 'facet>(
963 &mut self,
964 updates: &Updates<'mem, 'facet>,
965 _from: Option<Peek<'mem, 'facet>>,
966 _to: Option<Peek<'mem, 'facet>>,
967 change: ElementChange,
968 ) -> NodeId {
969 let temp = self.tree.new_node(LayoutNode::Element {
971 tag: Cow::Borrowed("_transparent"),
972 field_name: None,
973 attrs: Vec::new(),
974 changed_groups: Vec::new(),
975 change,
976 });
977
978 self.build_updates_children(temp, updates, "item");
980
981 let children: Vec<_> = temp.children(&self.tree).collect();
983
984 if children.len() == 1 {
985 let child = children[0];
987 child.detach(&mut self.tree);
988 temp.remove(&mut self.tree);
990 child
991 } else {
992 temp
995 }
996 }
997
998 fn build_enum_tuple_variant<'mem, 'facet>(
1006 &mut self,
1007 tag: Cow<'static, str>,
1008 updates: &Updates<'mem, 'facet>,
1009 inner_from: Option<Peek<'mem, 'facet>>,
1010 inner_to: Option<Peek<'mem, 'facet>>,
1011 change: ElementChange,
1012 ) -> NodeId {
1013 let interspersed = &updates.0;
1016
1017 if let Some(update_group) = &interspersed.first {
1020 let group_interspersed = &update_group.0;
1021
1022 if let Some(replace_group) = &group_interspersed.first
1024 && replace_group.removals.len() == 1
1025 && replace_group.additions.len() == 1
1026 {
1027 let from = replace_group.removals[0];
1028 let to = replace_group.additions[0];
1029
1030 let mut attrs = Vec::new();
1032
1033 if let (Ok(from_struct), Ok(to_struct)) = (from.into_struct(), to.into_struct())
1034 && let Type::User(UserType::Struct(ty)) = from.shape().ty
1035 {
1036 for (i, field) in ty.fields.iter().enumerate() {
1037 let from_value = from_struct.field(i).ok();
1038 let to_value = to_struct.field(i).ok();
1039
1040 match (from_value, to_value) {
1041 (Some(fv), Some(tv)) => {
1042 let from_formatted = self.format_peek(fv);
1044 let to_formatted = self.format_peek(tv);
1045
1046 if self.strings.get(from_formatted.span)
1047 != self.strings.get(to_formatted.span)
1048 {
1049 attrs.push(Attr::changed(
1051 Cow::Borrowed(field.name),
1052 field.name.len(),
1053 from_formatted,
1054 to_formatted,
1055 ));
1056 } else {
1057 if !should_skip_falsy(fv) {
1059 attrs.push(Attr::unchanged(
1060 Cow::Borrowed(field.name),
1061 field.name.len(),
1062 from_formatted,
1063 ));
1064 }
1065 }
1066 }
1067 (Some(fv), None) => {
1068 if !should_skip_falsy(fv) {
1070 let formatted = self.format_peek(fv);
1071 attrs.push(Attr::deleted(
1072 Cow::Borrowed(field.name),
1073 field.name.len(),
1074 formatted,
1075 ));
1076 }
1077 }
1078 (None, Some(tv)) => {
1079 if !should_skip_falsy(tv) {
1081 let formatted = self.format_peek(tv);
1082 attrs.push(Attr::inserted(
1083 Cow::Borrowed(field.name),
1084 field.name.len(),
1085 formatted,
1086 ));
1087 }
1088 }
1089 (None, None) => {
1090 }
1092 }
1093 }
1094 }
1095
1096 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
1097
1098 return self.tree.new_node(LayoutNode::Element {
1099 tag,
1100 field_name: None,
1101 attrs,
1102 changed_groups,
1103 change,
1104 });
1105 }
1106 }
1107
1108 let single_diff = {
1110 let mut found_diff: Option<&Diff<'mem, 'facet>> = None;
1111
1112 if let Some(update_group) = &interspersed.first {
1114 let group_interspersed = &update_group.0;
1115
1116 if let Some(diffs) = &group_interspersed.last
1118 && diffs.len() == 1
1119 && found_diff.is_none()
1120 {
1121 found_diff = Some(&diffs[0]);
1122 }
1123 for (diffs, _replace) in &group_interspersed.values {
1124 if diffs.len() == 1 && found_diff.is_none() {
1125 found_diff = Some(&diffs[0]);
1126 }
1127 }
1128 }
1129
1130 found_diff
1131 };
1132
1133 if let Some(diff) = single_diff {
1135 match diff {
1136 Diff::User {
1137 value:
1138 Value::Struct {
1139 updates,
1140 deletions,
1141 insertions,
1142 unchanged,
1143 },
1144 ..
1145 } => {
1146 return self.build_struct(
1148 tag.clone(),
1149 None,
1150 updates,
1151 deletions,
1152 insertions,
1153 unchanged,
1154 inner_from,
1155 inner_to,
1156 change,
1157 );
1158 }
1159 Diff::Replace { from, to } => {
1160 let mut attrs = Vec::new();
1163
1164 if let Ok(struct_peek) = from.into_struct()
1166 && let Type::User(UserType::Struct(ty)) = from.shape().ty
1167 {
1168 for (i, field) in ty.fields.iter().enumerate() {
1169 if let Ok(field_value) = struct_peek.field(i) {
1170 if should_skip_falsy(field_value) {
1171 continue;
1172 }
1173 let formatted = self.format_peek(field_value);
1174 attrs.push(Attr::deleted(
1175 Cow::Borrowed(field.name),
1176 field.name.len(),
1177 formatted,
1178 ));
1179 }
1180 }
1181 }
1182
1183 if let Ok(struct_peek) = to.into_struct()
1185 && let Type::User(UserType::Struct(ty)) = to.shape().ty
1186 {
1187 for (i, field) in ty.fields.iter().enumerate() {
1188 if let Ok(field_value) = struct_peek.field(i) {
1189 if should_skip_falsy(field_value) {
1190 continue;
1191 }
1192 let formatted = self.format_peek(field_value);
1193 attrs.push(Attr::inserted(
1194 Cow::Borrowed(field.name),
1195 field.name.len(),
1196 formatted,
1197 ));
1198 }
1199 }
1200 }
1201
1202 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
1203
1204 return self.tree.new_node(LayoutNode::Element {
1205 tag: tag.clone(),
1206 field_name: None,
1207 attrs,
1208 changed_groups,
1209 change,
1210 });
1211 }
1212 _ => {}
1213 }
1214 }
1215
1216 let node = self.tree.new_node(LayoutNode::Element {
1218 tag,
1219 field_name: None,
1220 attrs: Vec::new(),
1221 changed_groups: Vec::new(),
1222 change,
1223 });
1224
1225 self.build_updates_children(node, updates, "item");
1227
1228 node
1229 }
1230
1231 fn build_sequence(
1233 &mut self,
1234 updates: &Updates<'_, '_>,
1235 change: ElementChange,
1236 item_type: &'static str,
1237 ) -> NodeId {
1238 let node = self.tree.new_node(LayoutNode::Sequence {
1240 change,
1241 item_type,
1242 field_name: None,
1243 });
1244
1245 self.build_updates_children(node, updates, item_type);
1247
1248 node
1249 }
1250
1251 fn build_updates_children(
1257 &mut self,
1258 parent: NodeId,
1259 updates: &Updates<'_, '_>,
1260 _item_type: &'static str,
1261 ) {
1262 let mut items: Vec<(Peek<'_, '_>, ElementChange)> = Vec::new();
1264 let mut nested_diffs: Vec<&Diff<'_, '_>> = Vec::new();
1265
1266 let interspersed = &updates.0;
1267
1268 if let Some(update_group) = &interspersed.first {
1270 self.collect_updates_group_items(&mut items, &mut nested_diffs, update_group);
1271 }
1272
1273 for (unchanged_items, update_group) in &interspersed.values {
1275 for item in unchanged_items {
1277 items.push((*item, ElementChange::None));
1278 }
1279
1280 self.collect_updates_group_items(&mut items, &mut nested_diffs, update_group);
1281 }
1282
1283 if let Some(unchanged_items) = &interspersed.last {
1285 for item in unchanged_items {
1286 items.push((*item, ElementChange::None));
1287 }
1288 }
1289
1290 debug!(
1291 items_count = items.len(),
1292 nested_diffs_count = nested_diffs.len(),
1293 "collected sequence items"
1294 );
1295
1296 for diff in nested_diffs {
1298 debug!(diff_type = ?std::mem::discriminant(diff), "building nested diff");
1299 let (from_peek, to_peek) = match diff {
1301 Diff::User { .. } => {
1302 (None, None)
1306 }
1307 Diff::Replace { from, to } => (Some(*from), Some(*to)),
1308 _ => (None, None),
1309 };
1310 let child = self.build_diff(diff, from_peek, to_peek, ElementChange::None);
1311 parent.append(child, &mut self.tree);
1312 }
1313
1314 for (item_peek, item_change) in items {
1316 let child = self.build_peek(item_peek, item_change);
1317 parent.append(child, &mut self.tree);
1318 }
1319 }
1320
1321 fn collect_updates_group_items<'a, 'mem: 'a, 'facet: 'a>(
1324 &self,
1325 items: &mut Vec<(Peek<'mem, 'facet>, ElementChange)>,
1326 nested_diffs: &mut Vec<&'a Diff<'mem, 'facet>>,
1327 group: &'a UpdatesGroup<'mem, 'facet>,
1328 ) {
1329 let interspersed = &group.0;
1330
1331 if let Some(replace) = &interspersed.first {
1333 self.collect_replace_group_items(items, replace);
1334 }
1335
1336 for (diffs, replace) in &interspersed.values {
1338 for diff in diffs {
1340 nested_diffs.push(diff);
1341 }
1342 self.collect_replace_group_items(items, replace);
1343 }
1344
1345 if let Some(diffs) = &interspersed.last {
1347 for diff in diffs {
1348 nested_diffs.push(diff);
1349 }
1350 }
1351 }
1352
1353 fn collect_replace_group_items<'a, 'mem: 'a, 'facet: 'a>(
1355 &self,
1356 items: &mut Vec<(Peek<'mem, 'facet>, ElementChange)>,
1357 group: &'a ReplaceGroup<'mem, 'facet>,
1358 ) {
1359 for removal in &group.removals {
1361 items.push((*removal, ElementChange::Deleted));
1362 }
1363
1364 for addition in &group.additions {
1366 items.push((*addition, ElementChange::Inserted));
1367 }
1368 }
1369
1370 fn format_peek(&mut self, peek: Peek<'_, '_>) -> FormattedValue {
1372 let shape = peek.shape();
1373 debug!(
1374 type_id = %shape.type_identifier,
1375 def = ?shape.def,
1376 "format_peek"
1377 );
1378
1379 if let Def::Option(_) = shape.def
1381 && let Ok(opt) = peek.into_option()
1382 {
1383 if let Some(inner) = opt.value() {
1384 return self.format_peek(inner);
1385 }
1386 let (span, width) = self.strings.push_str("null");
1388 return FormattedValue::with_type(span, width, ValueType::Null);
1389 }
1390
1391 if let Some(precision) = self.opts.float_precision
1393 && let Type::Primitive(PrimitiveType::Numeric(NumericType::Float)) = shape.ty
1394 {
1395 if let Ok(v) = peek.get::<f64>() {
1397 let formatted = format!("{:.prec$}", v, prec = precision);
1398 let formatted = formatted.trim_end_matches('0').trim_end_matches('.');
1400 let (span, width) = self.strings.push_str(formatted);
1401 return FormattedValue::with_type(span, width, ValueType::Number);
1402 }
1403 if let Ok(v) = peek.get::<f32>() {
1404 let formatted = format!("{:.prec$}", v, prec = precision);
1405 let formatted = formatted.trim_end_matches('0').trim_end_matches('.');
1406 let (span, width) = self.strings.push_str(formatted);
1407 return FormattedValue::with_type(span, width, ValueType::Number);
1408 }
1409 }
1410
1411 let (span, width) = self.strings.format(|w| self.flavor.format_value(peek, w));
1412 let value_type = determine_value_type(peek);
1413 FormattedValue::with_type(span, width, value_type)
1414 }
1415
1416 fn finish(self, root: NodeId) -> Layout {
1418 Layout {
1419 strings: self.strings,
1420 tree: self.tree,
1421 root,
1422 }
1423 }
1424}
1425
1426#[cfg(test)]
1427mod tests {
1428 use super::*;
1429 use crate::layout::render::{RenderOptions, render_to_string};
1430 use crate::layout::{RustFlavor, XmlFlavor};
1431
1432 #[test]
1433 fn test_build_equal_diff() {
1434 let value = 42i32;
1435 let peek = Peek::new(&value);
1436 let diff = Diff::Equal { value: Some(peek) };
1437
1438 let layout = build_layout(&diff, peek, peek, &BuildOptions::default(), &RustFlavor);
1439
1440 let root = layout.get(layout.root).unwrap();
1442 assert!(matches!(root, LayoutNode::Text { .. }));
1443 }
1444
1445 #[test]
1446 fn test_build_replace_diff() {
1447 let from = 10i32;
1448 let to = 20i32;
1449 let diff = Diff::Replace {
1450 from: Peek::new(&from),
1451 to: Peek::new(&to),
1452 };
1453
1454 let layout = build_layout(
1455 &diff,
1456 Peek::new(&from),
1457 Peek::new(&to),
1458 &BuildOptions::default(),
1459 &RustFlavor,
1460 );
1461
1462 let root = layout.get(layout.root).unwrap();
1464 match root {
1465 LayoutNode::Element { tag, .. } => assert_eq!(tag.as_ref(), "_replace"),
1466 _ => panic!("expected Element node"),
1467 }
1468
1469 let children: Vec<_> = layout.children(layout.root).collect();
1470 assert_eq!(children.len(), 2);
1471 }
1472
1473 #[test]
1474 fn test_build_and_render_replace() {
1475 let from = 10i32;
1476 let to = 20i32;
1477 let diff = Diff::Replace {
1478 from: Peek::new(&from),
1479 to: Peek::new(&to),
1480 };
1481
1482 let layout = build_layout(
1483 &diff,
1484 Peek::new(&from),
1485 Peek::new(&to),
1486 &BuildOptions::default(),
1487 &RustFlavor,
1488 );
1489 let output = render_to_string(&layout, &RenderOptions::plain(), &XmlFlavor);
1490
1491 assert!(
1493 output.contains("10"),
1494 "output should contain old value: {}",
1495 output
1496 );
1497 assert!(
1498 output.contains("20"),
1499 "output should contain new value: {}",
1500 output
1501 );
1502 }
1503
1504 #[test]
1505 fn test_build_options_default() {
1506 let opts = BuildOptions::default();
1507 assert_eq!(opts.max_line_width, 80);
1508 assert_eq!(opts.max_unchanged_fields, 5);
1509 assert_eq!(opts.collapse_threshold, 3);
1510 }
1511}