1use std::fmt::Debug;
2use std::fmt::Display;
3use std::fmt::Write as _;
4
5use super::helpers::quote_string;
6use super::Shape;
7use super::ShapeCase;
8use crate::helpers::quote_non_identifier;
9use crate::name::Name;
10
11const UNIT_OF_INDENTATION: &str = " ";
12const WRAP_COLUMN: usize = 60;
13
14const ELIDED: &str = "…";
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct PrintLimits {
22 pub max_depth: usize,
29 pub max_width: usize,
34}
35
36impl Default for PrintLimits {
37 fn default() -> Self {
38 Self {
39 max_depth: 3,
40 max_width: 8,
41 }
42 }
43}
44
45impl Shape {
46 #[must_use]
53 pub fn pretty_print(&self) -> String {
54 self.pretty_print_at_depth(0)
55 }
56
57 fn pretty_print_at_depth(&self, depth: usize) -> String {
58 let pretty_shape =
59 self.pretty_print_recursive(depth, usize::MAX, Shape::pretty_print_at_depth);
60 self.append_error_suffix(pretty_shape)
61 }
62
63 #[must_use]
73 pub fn pretty_print_bounded(&self, limits: PrintLimits) -> String {
74 self.pretty_print_bounded_at_depth(0, limits.max_depth, limits.max_width)
75 }
76
77 fn pretty_print_bounded_at_depth(
78 &self,
79 depth: usize,
80 remaining_depth: usize,
81 max_width: usize,
82 ) -> String {
83 let child_remaining_depth = match self.case() {
84 ShapeCase::Array { .. } | ShapeCase::Object { .. } => {
85 if remaining_depth == 0 {
86 return self.append_error_suffix(ELIDED.to_string());
90 }
91 remaining_depth - 1
92 }
93 ShapeCase::One(members) if has_several_composite_members(members.iter(), max_width) => {
103 remaining_depth.saturating_sub(1)
104 }
105 ShapeCase::All(members) if has_several_composite_members(members.iter(), max_width) => {
106 remaining_depth.saturating_sub(1)
107 }
108 _ => remaining_depth,
109 };
110 let pretty_shape = self.pretty_print_recursive(depth, max_width, |child, child_depth| {
111 child.pretty_print_bounded_at_depth(child_depth, child_remaining_depth, max_width)
112 });
113 self.append_error_suffix(pretty_shape)
114 }
115
116 #[must_use]
119 pub fn pretty_print_without_errors(&self) -> String {
120 self.pretty_print_without_errors_at_depth(0)
121 }
122
123 fn pretty_print_without_errors_at_depth(&self, depth: usize) -> String {
124 self.pretty_print_recursive(
125 depth,
126 usize::MAX,
127 Shape::pretty_print_without_errors_at_depth,
128 )
129 }
130
131 #[must_use]
135 pub fn pretty_print_with_names(&self) -> String {
136 self.pretty_print_with_names_at_depth(0)
137 }
138
139 fn pretty_print_with_names_at_depth(&self, depth: usize) -> String {
140 let pretty_shape =
141 self.pretty_print_recursive(depth, usize::MAX, Shape::pretty_print_with_names_at_depth);
142
143 let mut result = pretty_shape;
144
145 result = self.append_error_suffix(result);
147
148 if self.names().count() > 0 {
150 let joined = self
151 .names()
152 .map(Name::to_string)
153 .collect::<Vec<_>>()
154 .join(", ");
155 result = format!("{result} (aka {joined})");
159 }
160
161 result
162 }
163
164 fn append_error_suffix(&self, pretty_shape: String) -> String {
166 let error_messages: Vec<_> = self
167 .own_errors()
168 .map(|e| quote_string(&e.message))
169 .collect();
170 if error_messages.is_empty() {
171 return pretty_shape;
172 }
173 format!("{pretty_shape} (err {})", error_messages.join(", "))
174 }
175
176 #[allow(clippy::too_many_lines)]
177 fn pretty_print_recursive(
178 &self,
179 depth: usize,
180 max_width: usize,
181 print: impl Fn(&Shape, usize) -> String,
182 ) -> String {
183 fn print_flexible_wrapper(
184 prefix: &str,
185 pretty_children: impl IntoIterator<Item = String>,
186 suffix: &str,
187 depth: usize,
188 ) -> String {
189 let pretty_children = pretty_children.into_iter().collect::<Vec<_>>();
190
191 let trimmed_prefix = prefix.trim();
195 let trimmed_suffix = suffix.trim();
196
197 if pretty_children.is_empty() {
198 return format!("{trimmed_prefix}{trimmed_suffix}");
201 }
202
203 if let (1, Some(only)) = (pretty_children.len(), pretty_children.first()) {
204 return format!("{prefix}{only}{suffix}");
209 }
210
211 if pretty_children
212 .iter()
213 .all(|pretty| pretty.lines().count() <= 1)
214 {
215 let one_line = format!("{prefix}{}{suffix}", pretty_children.join(", "));
234 if one_line.len() <= WRAP_COLUMN {
235 return one_line;
236 }
237 }
240
241 let mut result = format!("{trimmed_prefix}\n");
242 let child_indent = UNIT_OF_INDENTATION.repeat(depth + 1);
243
244 for pretty in &pretty_children {
245 let _ = writeln!(result, "{child_indent}{pretty},");
246 }
247
248 let suffix_indent = UNIT_OF_INDENTATION.repeat(depth);
249 let _ = write!(result, "{suffix_indent}{trimmed_suffix}");
250
251 result
252 }
253
254 match self.case() {
255 ShapeCase::Bool(Some(b)) => b.to_string(),
256 ShapeCase::Bool(None) => "Bool".to_string(),
257 ShapeCase::String(Some(s)) => quote_string(s.as_str()),
258 ShapeCase::String(None) => "String".to_string(),
259 ShapeCase::Int(Some(i)) => i.to_string(),
260 ShapeCase::Int(None) => "Int".to_string(),
261 ShapeCase::Float => "Float".to_string(),
262 ShapeCase::Null => "null".to_string(), ShapeCase::Unknown => "Unknown".to_string(),
265
266 ShapeCase::None => "None".to_string(),
271
272 ShapeCase::Array { prefix, tail } => {
273 let mut pretty_prefix = prefix
274 .iter()
275 .take(max_width)
276 .map(|shape| print(shape, depth + 1))
277 .collect::<Vec<_>>();
278 if prefix.len() > max_width {
279 pretty_prefix.push(ELIDED.to_string());
280 }
281
282 if !tail.is_none() {
283 let pretty_tail_inner = print(tail, depth + 1);
291 let rest_marker = if pretty_tail_inner == "Unknown" {
292 "...".to_string()
293 } else {
294 format!("...{pretty_tail_inner}")
295 };
296 pretty_prefix.push(rest_marker);
297 }
298
299 print_flexible_wrapper("[", pretty_prefix, "]", depth)
300 }
301
302 ShapeCase::Object { fields, rest } => {
303 let mut sorted_properties = fields.iter().collect::<Vec<_>>();
307 sorted_properties.sort_by_key(|(key, _)| key.as_str());
308
309 if fields.len() > max_width {
310 let (with_errors, without_errors): (Vec<_>, Vec<_>) = sorted_properties
316 .into_iter()
317 .partition(|(_, value)| value.has_own_errors());
318 sorted_properties = with_errors
319 .into_iter()
320 .chain(without_errors)
321 .take(max_width)
322 .collect();
323 sorted_properties.sort_by_key(|(key, _)| key.as_str());
324 }
325
326 let mut pretty_properties = sorted_properties
327 .into_iter()
328 .map(|(key, value)| {
329 format!("{}: {}", quote_non_identifier(key), print(value, depth + 1))
330 })
331 .collect::<Vec<_>>();
332 if fields.len() > max_width {
333 pretty_properties.push(ELIDED.to_string());
334 }
335
336 let has_fields = !pretty_properties.is_empty();
341
342 if !rest.is_none() {
343 let pretty_rest_inner = print(rest, depth + 1);
351 let rest_marker = if pretty_rest_inner == "Unknown" {
352 "...".to_string()
353 } else {
354 format!("...{pretty_rest_inner}")
355 };
356 pretty_properties.push(rest_marker);
357 }
358
359 let (prefix, suffix) = if has_fields { ("{ ", " }") } else { ("{", "}") };
366
367 print_flexible_wrapper(prefix, pretty_properties, suffix, depth)
368 }
369
370 ShapeCase::One(shapes) => {
371 let pretty = print_flexible_wrapper(
372 "One<",
373 print_members(shapes.iter(), max_width, |child_shape| {
374 print(child_shape, depth + 1)
375 }),
376 ">",
377 depth,
378 );
379
380 if pretty == "One<>" {
383 "Never".to_string()
384 } else {
385 pretty
386 }
387 }
388
389 ShapeCase::All(shapes) => print_flexible_wrapper(
390 "All<",
391 print_members(shapes.iter(), max_width, |child_shape| {
392 print(child_shape, depth + 1)
393 }),
394 ">",
395 depth,
396 ),
397
398 ShapeCase::Name(name, _weak) => name.to_string(),
399 }
400 }
401}
402
403fn print_members<'a>(
406 members: impl Iterator<Item = &'a Shape>,
407 max_width: usize,
408 print: impl Fn(&Shape) -> String,
409) -> Vec<String> {
410 let (kept, elided) = kept_members(members, max_width);
411 let mut pretty = kept.into_iter().map(print).collect::<Vec<_>>();
412 if elided {
413 pretty.push(ELIDED.to_string());
414 }
415 let mut seen_bare_marker = false;
419 pretty.retain(|member| {
420 if member != ELIDED {
421 return true;
422 }
423 let first = !seen_bare_marker;
424 seen_bare_marker = true;
425 first
426 });
427 pretty
428}
429
430fn kept_members<'a>(
436 members: impl Iterator<Item = &'a Shape>,
437 max_width: usize,
438) -> (Vec<&'a Shape>, bool) {
439 let members = members.collect::<Vec<_>>();
440 if members.len() <= max_width {
441 return (members, false);
442 }
443 let (with_errors, without_errors): (Vec<usize>, Vec<usize>) =
444 (0..members.len()).partition(|&index| members[index].has_own_errors());
445 let mut kept = with_errors
446 .into_iter()
447 .chain(without_errors)
448 .take(max_width)
449 .collect::<Vec<_>>();
450 kept.sort_unstable();
451 (kept.into_iter().map(|index| members[index]).collect(), true)
452}
453
454fn has_several_composite_members<'a>(
457 members: impl Iterator<Item = &'a Shape>,
458 max_width: usize,
459) -> bool {
460 kept_members(members, max_width)
461 .0
462 .into_iter()
463 .filter(|member| {
464 matches!(
465 member.case(),
466 ShapeCase::Array { .. }
467 | ShapeCase::Object { .. }
468 | ShapeCase::One(_)
469 | ShapeCase::All(_)
470 )
471 })
472 .nth(1)
473 .is_some()
474}
475
476impl Debug for Shape {
477 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478 write!(f, "Shape({})", self.pretty_print_with_names())
479 }
480}
481
482impl Display for Shape {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 write!(f, "{}", self.pretty_print())
485 }
486}
487
488#[cfg(test)]
489mod bounded_tests {
490 use indexmap::IndexMap;
491
492 use super::PrintLimits;
493 use crate::Shape;
494
495 fn record(fields: impl IntoIterator<Item = (impl Into<String>, Shape)>) -> Shape {
496 Shape::closed_record(
497 fields
498 .into_iter()
499 .map(|(key, value)| (key.into(), value))
500 .collect::<IndexMap<_, _>>(),
501 [],
502 )
503 }
504
505 fn limits(max_depth: usize, max_width: usize) -> PrintLimits {
506 PrintLimits {
507 max_depth,
508 max_width,
509 }
510 }
511
512 #[test]
513 fn unlimited_matches_pretty_print() {
514 let shape = Shape::one(
515 [
516 record([
517 ("id", Shape::int([])),
518 ("tags", Shape::list(Shape::string([]), [])),
519 (
520 "nested",
521 record([(
522 "deep",
523 Shape::closed_tuple([Shape::bool([]), Shape::null([])], []),
524 )]),
525 ),
526 ]),
527 Shape::null([]),
528 ],
529 [],
530 );
531 assert_eq!(
532 shape.pretty_print_bounded(limits(usize::MAX, usize::MAX)),
533 shape.pretty_print(),
534 );
535 }
536
537 #[test]
538 fn elides_below_max_depth() {
539 let shape = record([("a", record([("b", record([("c", Shape::int([]))]))]))]);
540 assert_eq!(shape.pretty_print_bounded(limits(0, 8)), "…");
541 assert_eq!(shape.pretty_print_bounded(limits(1, 8)), "{ a: … }");
542 assert_eq!(shape.pretty_print_bounded(limits(2, 8)), "{ a: { b: … } }");
543 assert_eq!(
544 shape.pretty_print_bounded(limits(3, 8)),
545 "{ a: { b: { c: Int } } }",
546 );
547 }
548
549 #[test]
550 fn nullable_unions_do_not_count_as_a_level() {
551 let nullable = |shape| Shape::one([shape, Shape::null([])], []);
552 let shape = nullable(record([("a", nullable(record([("b", Shape::int([]))])))]));
553 assert_eq!(
554 shape.pretty_print_bounded(limits(1, 8)),
555 "One<{ a: One<…, null> }, null>",
556 );
557 }
558
559 #[test]
560 fn elides_past_max_width() {
561 let object = record([
562 ("d", Shape::int([])),
563 ("a", Shape::int([])),
564 ("c", Shape::int([])),
565 ("b", Shape::int([])),
566 ]);
567 assert_eq!(
569 object.pretty_print_bounded(limits(3, 2)),
570 "{ a: Int, b: Int, … }"
571 );
572
573 let tuple = Shape::closed_tuple([Shape::int([]), Shape::bool([]), Shape::null([])], []);
574 assert_eq!(tuple.pretty_print_bounded(limits(3, 2)), "[Int, Bool, …]");
575
576 let one = Shape::one([Shape::int([]), Shape::bool([]), Shape::string([])], []);
577 assert_eq!(one.pretty_print_bounded(limits(3, 2)), "One<Int, Bool, …>");
578
579 assert_eq!(one.pretty_print_bounded(limits(3, 3)), one.pretty_print());
581 }
582
583 #[test]
584 fn bounded_on_shared_subtrees() {
585 let mut shape = Shape::int([]);
589 for _ in 0..40 {
590 shape = record([("a", shape.clone()), ("b", shape)]);
591 }
592 let printed = shape.pretty_print_bounded(PrintLimits::default());
593 assert!(printed.len() < 1_000, "{} bytes:\n{printed}", printed.len());
594 assert!(printed.contains('…'));
595 }
596
597 #[test]
598 fn unions_of_several_composites_count_as_a_level() {
599 let a = record([("a", Shape::int([]))]);
600 let b = record([("b", Shape::int([]))]);
601 let shape = record([("x", Shape::one([a.clone(), b], []))]);
602 assert_eq!(shape.pretty_print_bounded(limits(1, 8)), "{ x: One<…> }");
604 assert_eq!(shape.pretty_print_bounded(limits(2, 8)), "{ x: One<…> }");
605 assert_eq!(
606 shape.pretty_print_bounded(limits(3, 8)),
607 "{ x: One<{ a: Int }, { b: Int }> }",
608 );
609 let nullable = record([("x", Shape::one([a, Shape::null([])], []))]);
611 assert_eq!(
612 nullable.pretty_print_bounded(limits(2, 8)),
613 "{ x: One<{ a: Int }, null> }",
614 );
615 }
616
617 #[test]
618 fn bounded_on_union_heavy_shape() {
619 let mut variants = (0..8)
623 .map(|v| record((0..8).map(|i| (format!("v{v}_f{i}"), Shape::int([])))))
624 .collect::<Vec<_>>();
625 for _ in 1..8 {
626 let union = Shape::one(variants, []);
627 variants = (0..8)
628 .map(|v| record((0..8).map(|i| (format!("v{v}_f{i}"), union.clone()))))
629 .collect();
630 }
631 let printed = variants[0].pretty_print_bounded(PrintLimits::default());
632 assert!(printed.len() < 32_000, "{} bytes", printed.len());
633 assert!(printed.contains("One<…>"));
634 }
635
636 #[test]
637 fn elided_shapes_keep_their_errors() {
638 let inner = Shape::error_with_partial(
639 "field c is not a String",
640 record([("c", Shape::int([]))]),
641 [],
642 );
643 let shape = record([("a", record([("b", inner)]))]);
644 assert_eq!(
645 shape.pretty_print_bounded(limits(2, 8)),
646 r#"{ a: { b: … (err "field c is not a String") } }"#,
647 );
648 assert_eq!(shape.pretty_print_bounded(limits(0, 8)), "…");
650 let top = Shape::error_with_partial("top", record([("a", Shape::int([]))]), []);
651 assert_eq!(top.pretty_print_bounded(limits(0, 8)), r#"… (err "top")"#);
652 }
653
654 #[test]
655 fn width_cutoff_keeps_fields_with_errors() {
656 let shape = record([
657 ("a", Shape::int([])),
658 ("b", Shape::int([])),
659 ("z", Shape::error("z is bad", [])),
660 ]);
661 assert_eq!(
662 shape.pretty_print_bounded(limits(3, 2)),
663 r#"{ a: Int, z: Unknown (err "z is bad"), … }"#,
664 );
665 assert_eq!(
667 shape.pretty_print_bounded(limits(3, 3)),
668 shape.pretty_print()
669 );
670 }
671
672 #[test]
673 fn elided_union_members_fold_into_one_marker() {
674 let shape = Shape::one(
675 [
676 record([("a", Shape::int([]))]),
677 record([("b", Shape::int([]))]),
678 Shape::null([]),
679 ],
680 [],
681 );
682 assert_eq!(shape.pretty_print_bounded(limits(0, 8)), "One<…, null>");
683 }
684
685 #[test]
686 fn tails_and_rests_at_the_cutoff() {
687 let object = record([("x", Shape::int([]))]);
688 let lists = Shape::list(Shape::list(object.clone(), []), []);
689 assert_eq!(lists.pretty_print_bounded(limits(1, 8)), "[...…]");
690 assert_eq!(
691 Shape::dict(object, []).pretty_print_bounded(limits(1, 8)),
692 "{...…}"
693 );
694 }
695
696 #[test]
697 fn zero_width() {
698 let shape = record([("a", Shape::int([])), ("b", Shape::int([]))]);
699 assert_eq!(shape.pretty_print_bounded(limits(3, 0)), "{ … }");
700 assert_eq!(
701 Shape::closed_tuple([Shape::int([])], []).pretty_print_bounded(limits(3, 0)),
702 "[…]",
703 );
704 assert_eq!(
705 Shape::one([Shape::int([]), Shape::bool([])], []).pretty_print_bounded(limits(3, 0)),
706 "One<…>",
707 );
708 }
709
710 #[test]
711 fn every_bare_marker_folds_even_when_not_adjacent() {
712 let shape = Shape::one(
713 [
714 record([("a", Shape::int([]))]),
715 Shape::null([]),
716 record([("b", Shape::int([]))]),
717 ],
718 [],
719 );
720 assert_eq!(shape.pretty_print_bounded(limits(0, 8)), "One<…, null>");
721
722 let shape = Shape::one(
724 [
725 Shape::error_with_partial("a bad", record([("a", Shape::int([]))]), []),
726 record([("b", Shape::int([]))]),
727 record([("c", Shape::int([]))]),
728 ],
729 [],
730 );
731 assert_eq!(
732 shape.pretty_print_bounded(limits(0, 8)),
733 r#"One<… (err "a bad"), …>"#,
734 );
735 }
736
737 #[test]
738 fn union_charge_counts_only_printed_members() {
739 let a = record([("a", Shape::int([]))]);
740 let b = record([("b", Shape::int([]))]);
741 let shape = record([("x", Shape::one([a, b], []))]);
742 assert_eq!(
744 shape.pretty_print_bounded(limits(2, 1)),
745 "{ x: One<{ a: Int }, …> }",
746 );
747 }
748
749 #[test]
750 fn union_of_an_object_and_an_array_counts_as_a_level() {
751 let shape = record([(
752 "x",
753 Shape::one(
754 [
755 record([("a", Shape::int([]))]),
756 Shape::list(Shape::int([]), []),
757 ],
758 [],
759 ),
760 )]);
761 assert_eq!(shape.pretty_print_bounded(limits(2, 8)), "{ x: One<…> }");
762 assert_eq!(
763 shape.pretty_print_bounded(limits(3, 8)),
764 "{ x: One<{ a: Int }, [...Int]> }",
765 );
766 }
767
768 #[test]
769 fn width_cutoff_keeps_union_members_with_errors() {
770 let shape = Shape::one(
771 [
772 Shape::int([]),
773 Shape::bool([]),
774 Shape::error_with_partial("member bad", Shape::string([]), []),
775 ],
776 [],
777 );
778 assert_eq!(
780 shape.pretty_print_bounded(limits(3, 2)),
781 r#"One<Int, String (err "member bad"), …>"#,
782 );
783 assert_eq!(
785 shape.pretty_print_bounded(limits(3, 3)),
786 shape.pretty_print()
787 );
788 }
789
790 #[test]
791 fn tuple_elements_stay_positional_at_the_cutoff() {
792 let shape = Shape::closed_tuple(
793 [
794 Shape::int([]),
795 Shape::int([]),
796 Shape::error("third bad", []),
797 ],
798 [],
799 );
800 assert_eq!(shape.pretty_print_bounded(limits(3, 2)), "[Int, Int, …]");
801 }
802
803 #[test]
804 fn more_error_fields_than_the_width_allows() {
805 let shape = record([
806 ("c", Shape::error("ec", [])),
807 ("a", Shape::error("ea", [])),
808 ("b", Shape::error("eb", [])),
809 ("d", Shape::int([])),
810 ]);
811 assert_eq!(
813 shape.pretty_print_bounded(limits(3, 2)),
814 r#"{ a: Unknown (err "ea"), b: Unknown (err "eb"), … }"#,
815 );
816 }
817
818 #[test]
819 fn intersection_of_several_composites_counts_as_a_level() {
820 let shape = record([(
822 "x",
823 Shape::all(
824 [
825 record([("a", Shape::int([]))]),
826 Shape::list(Shape::int([]), []),
827 ],
828 [],
829 ),
830 )]);
831 assert_eq!(shape.pretty_print_bounded(limits(2, 8)), "{ x: All<…> }");
832 assert_eq!(
833 shape.pretty_print_bounded(limits(3, 8)),
834 "{ x: All<[...Int], { a: Int }> }",
835 );
836 }
837}