facet_pretty/printer.rs
1//! Pretty printer implementation for Facet types
2
3use std::{
4 collections::{HashMap, VecDeque},
5 fmt::{self, Write},
6 hash::{DefaultHasher, Hash, Hasher},
7 str,
8};
9
10use facet_core::{Facet, FieldFlags, TypeNameOpts, VariantKind};
11use facet_reflect::{Peek, PeekValue};
12
13use crate::color::ColorGenerator;
14use facet_ansi::Stylize;
15
16/// A formatter for pretty-printing Facet types
17pub struct PrettyPrinter {
18 indent_size: usize,
19 max_depth: Option<usize>,
20 color_generator: ColorGenerator,
21 use_colors: bool,
22 list_u8_as_bytes: bool,
23}
24
25impl Default for PrettyPrinter {
26 fn default() -> Self {
27 Self {
28 indent_size: 2,
29 max_depth: None,
30 color_generator: ColorGenerator::default(),
31 use_colors: true,
32 list_u8_as_bytes: true,
33 }
34 }
35}
36
37/// Stack state for iterative formatting
38enum StackState {
39 Start,
40 ProcessStructField { field_index: usize },
41 ProcessListItem { item_index: usize },
42 ProcessBytesItem { item_index: usize },
43 ProcessMapEntry,
44 Finish,
45 OptionFinish,
46}
47
48/// Stack item for iterative traversal
49struct StackItem<'a> {
50 peek: Peek<'a>,
51 format_depth: usize,
52 type_depth: usize,
53 state: StackState,
54}
55
56impl PrettyPrinter {
57 /// Create a new PrettyPrinter with default settings
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 /// Set the indentation size
63 pub fn with_indent_size(mut self, size: usize) -> Self {
64 self.indent_size = size;
65 self
66 }
67
68 /// Set the maximum depth for recursive printing
69 pub fn with_max_depth(mut self, depth: usize) -> Self {
70 self.max_depth = Some(depth);
71 self
72 }
73
74 /// Set the color generator
75 pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
76 self.color_generator = generator;
77 self
78 }
79
80 /// Enable or disable colors
81 pub fn with_colors(mut self, use_colors: bool) -> Self {
82 self.use_colors = use_colors;
83 self
84 }
85
86 /// Format a value to a string
87 pub fn format<T: Facet>(&self, value: &T) -> String {
88 let peek = Peek::new(value);
89
90 let mut output = String::new();
91 self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
92 .expect("Formatting failed");
93
94 output
95 }
96
97 /// Format a value to a formatter
98 pub fn format_to<T: Facet>(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 let peek = Peek::new(value);
100 self.format_peek_internal(peek, f, 0, 0, &mut HashMap::new())
101 }
102
103 /// Format a Peek value to a string
104 pub fn format_peek(&self, peek: Peek<'_>) -> String {
105 let mut output = String::new();
106 self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
107 .expect("Formatting failed");
108 output
109 }
110
111 /// Internal method to format a Peek value
112 pub(crate) fn format_peek_internal(
113 &self,
114 peek: Peek<'_>,
115 f: &mut impl Write,
116 format_depth: usize,
117 type_depth: usize,
118 visited: &mut HashMap<*const (), usize>,
119 ) -> fmt::Result {
120 // Create a queue for our stack items
121 let mut stack = VecDeque::new();
122
123 // Push the initial item
124 stack.push_back(StackItem {
125 peek,
126 format_depth,
127 type_depth,
128 state: StackState::Start,
129 });
130
131 // Process items until the stack is empty
132 while let Some(mut item) = stack.pop_back() {
133 match item.state {
134 StackState::Start => {
135 // Check if we've reached the maximum depth
136 if let Some(max_depth) = self.max_depth {
137 if item.format_depth > max_depth {
138 self.write_punctuation(f, "[")?;
139 write!(f, "...")?;
140 continue;
141 }
142 }
143
144 // Get the data pointer for cycle detection
145 let ptr = unsafe { item.peek.data().as_ptr() };
146
147 // Check for cycles - if we've seen this pointer before at a different type_depth
148 if let Some(&ptr_type_depth) = visited.get(&ptr) {
149 // If the current type_depth is significantly deeper than when we first saw this pointer,
150 // we have a true cycle, not just a transparent wrapper
151 if item.type_depth > ptr_type_depth + 1 {
152 self.write_type_name(f, &item.peek)?;
153 self.write_punctuation(f, " { ")?;
154 self.write_comment(
155 f,
156 &format!(
157 "/* cycle detected at {:p} (first seen at type_depth {}) */",
158 ptr, ptr_type_depth
159 ),
160 )?;
161 self.write_punctuation(f, " }")?;
162 continue;
163 }
164 } else {
165 // First time seeing this pointer, record its type_depth
166 visited.insert(ptr, item.type_depth);
167 }
168
169 // Process based on the peek variant
170 match item.peek {
171 Peek::Value(value) => {
172 self.format_value(value, f)?;
173 }
174 Peek::Option(option) => {
175 // Print the Option name
176 self.write_type_name(f, &option)?;
177
178 if option.is_some() {
179 self.write_punctuation(f, "::Some(")?;
180
181 if let Some(inner_value) = option.value() {
182 // Create a custom stack item for Option::Some value
183 let start_item = StackItem {
184 peek: inner_value,
185 format_depth: item.format_depth,
186 type_depth: item.type_depth + 1,
187 state: StackState::Start,
188 };
189
190 // Add a special close parenthesis item
191 let close_paren_item = StackItem {
192 peek: Peek::Option(option),
193 format_depth: item.format_depth,
194 type_depth: item.type_depth,
195 state: StackState::OptionFinish,
196 };
197
198 // Process the value first, then handle closing
199 stack.push_back(close_paren_item);
200 stack.push_back(start_item);
201 }
202
203 // Skip to next item
204 continue;
205 } else {
206 self.write_punctuation(f, "::None")?;
207 }
208 }
209 Peek::Struct(struct_) => {
210 // When recursing into a struct, always increment format_depth
211 // Only increment type_depth if we're moving to a different address
212 let new_type_depth =
213 if core::ptr::eq(unsafe { struct_.data().as_ptr() }, ptr) {
214 item.type_depth // Same pointer, don't increment type_depth
215 } else {
216 item.type_depth + 1 // Different pointer, increment type_depth
217 };
218
219 // Get struct doc comments from the shape
220 let doc_comments = struct_.shape().doc;
221 if !doc_comments.is_empty() {
222 for line in doc_comments {
223 self.write_comment(f, &format!("///{}", line))?;
224 writeln!(f)?;
225 }
226 }
227
228 // Print the struct name
229 self.write_type_name(f, &struct_)?;
230 self.write_punctuation(f, " {")?;
231
232 if struct_.field_count() == 0 {
233 self.write_punctuation(f, "}")?;
234 continue;
235 }
236
237 writeln!(f)?;
238
239 // Push back the item with the next state to continue processing fields
240 item.state = StackState::ProcessStructField { field_index: 0 };
241 item.format_depth += 1;
242 item.type_depth = new_type_depth;
243 stack.push_back(item);
244 }
245 Peek::List(list) => {
246 // When recursing into a list, always increment format_depth
247 // Only increment type_depth if we're moving to a different address
248 let new_type_depth =
249 if core::ptr::eq(unsafe { list.data().as_ptr() }, ptr) {
250 item.type_depth // Same pointer, don't increment type_depth
251 } else {
252 item.type_depth + 1 // Different pointer, increment type_depth
253 };
254
255 // Print the list name
256 self.write_type_name(f, &list)?;
257
258 if list.def().t.is_type::<u8>() && self.list_u8_as_bytes {
259 // Push back the item with the next state to continue processing list items
260 item.state = StackState::ProcessBytesItem { item_index: 0 };
261 writeln!(f)?;
262 write!(f, " ")?;
263
264 // TODO: write all the bytes here instead?
265 } else {
266 // Push back the item with the next state to continue processing list items
267 item.state = StackState::ProcessListItem { item_index: 0 };
268 self.write_punctuation(f, " [")?;
269 writeln!(f)?;
270 }
271
272 item.format_depth += 1;
273 item.type_depth = new_type_depth;
274 stack.push_back(item);
275 }
276 Peek::Map(map) => {
277 // Print the map name
278 self.write_type_name(f, &map)?;
279 self.write_punctuation(f, " {")?;
280 writeln!(f)?;
281
282 // Push back the item with the next state to continue processing map
283 item.state = StackState::ProcessMapEntry;
284 item.format_depth += 1;
285 // When recursing into a map, always increment format_depth
286 // Only increment type_depth if we're moving to a different address
287 item.type_depth = if core::ptr::eq(unsafe { map.data().as_ptr() }, ptr)
288 {
289 item.type_depth // Same pointer, don't increment type_depth
290 } else {
291 item.type_depth + 1 // Different pointer, increment type_depth
292 };
293 stack.push_back(item);
294 }
295 Peek::Enum(enum_) => {
296 // When recursing into an enum, increment format_depth
297 // Only increment type_depth if we're moving to a different address
298 let _new_type_depth =
299 if core::ptr::eq(unsafe { enum_.data().as_ptr() }, ptr) {
300 item.type_depth // Same pointer, don't increment type_depth
301 } else {
302 item.type_depth + 1 // Different pointer, increment type_depth
303 };
304
305 // Get the active variant
306 let variant = enum_.active_variant();
307
308 // Get enum and variant doc comments
309 let doc_comments = enum_.shape().doc;
310
311 // Display doc comments before the type name
312 for line in doc_comments {
313 self.write_comment(f, &format!("///{}", line))?;
314 writeln!(f)?;
315 }
316
317 // Show variant docs
318 for line in variant.doc {
319 self.write_comment(f, &format!("///{}", line))?;
320 writeln!(f)?;
321 }
322
323 // Print the enum name and separator
324 self.write_type_name(f, &enum_)?;
325 self.write_punctuation(f, "::")?;
326
327 // Variant docs are already handled above
328
329 // Get the active variant name
330 let variant_name = enum_.variant_name_active();
331
332 // Apply color for variant name
333 if self.use_colors {
334 write!(f, "{}", variant_name.bold())?;
335 } else {
336 write!(f, "{}", variant_name)?;
337 }
338
339 // Process the variant fields based on the variant kind
340 match enum_.variant_kind_active() {
341 VariantKind::Unit => {
342 // Unit variant has no fields, nothing more to print
343 }
344 VariantKind::Tuple { .. } => {
345 // Tuple variant, print the fields like a tuple
346 self.write_punctuation(f, "(")?;
347
348 // Check if there are any fields to print
349 let has_fields = enum_.fields().count() > 0;
350
351 if !has_fields {
352 self.write_punctuation(f, ")")?;
353 continue;
354 }
355
356 writeln!(f)?;
357
358 // Push back item to process fields
359 item.state = StackState::ProcessStructField { field_index: 0 };
360 item.format_depth += 1;
361 stack.push_back(item);
362 }
363 VariantKind::Struct { .. } => {
364 // Struct variant, print the fields like a struct
365 self.write_punctuation(f, " {")?;
366
367 // Check if there are any fields to print
368 let has_fields = enum_.fields().count() > 0;
369
370 if !has_fields {
371 self.write_punctuation(f, " }")?;
372 continue;
373 }
374
375 writeln!(f)?;
376
377 // Push back item to process fields
378 item.state = StackState::ProcessStructField { field_index: 0 };
379 item.format_depth += 1;
380 stack.push_back(item);
381 }
382 _ => {
383 // Other variant kinds that might be added in the future
384 write!(f, " /* unsupported variant kind */")?;
385 }
386 }
387 }
388 _ => {
389 write!(f, "unsupported peek variant: {:?}", item.peek)?;
390 }
391 }
392 }
393 StackState::ProcessStructField { field_index } => {
394 // Handle both struct and enum fields
395 if let Peek::Struct(struct_) = item.peek {
396 let fields: Vec<_> = struct_.fields_with_metadata().collect();
397
398 if field_index >= fields.len() {
399 // All fields processed, write closing brace
400 write!(
401 f,
402 "{:width$}{}",
403 "",
404 self.style_punctuation("}"),
405 width = (item.format_depth - 1) * self.indent_size
406 )?;
407 continue;
408 }
409
410 let (_, field_name, field_value, field) = &fields[field_index];
411
412 // Field doc comment
413 if !field.doc.is_empty() {
414 // Only add new line if not the first field
415 if field_index > 0 {
416 writeln!(f)?;
417 }
418 // Hard-code consistent indentation for doc comments
419 for line in field.doc {
420 // Use exactly the same indentation as fields (2 spaces)
421 write!(
422 f,
423 "{:width$}",
424 "",
425 width = item.format_depth * self.indent_size
426 )?;
427 self.write_comment(f, &format!("///{}", line))?;
428 writeln!(f)?;
429 }
430 }
431
432 // Field name
433 write!(
434 f,
435 "{:width$}",
436 "",
437 width = item.format_depth * self.indent_size
438 )?;
439 self.write_field_name(f, field_name)?;
440 self.write_punctuation(f, ": ")?;
441
442 // Check if field is sensitive
443 if field.flags.contains(FieldFlags::SENSITIVE) {
444 // Field value is sensitive, use write_redacted
445 self.write_redacted(f, "[REDACTED]")?;
446 self.write_punctuation(f, ",")?;
447 writeln!(f)?;
448
449 item.state = StackState::ProcessStructField {
450 field_index: field_index + 1,
451 };
452 stack.push_back(item);
453 } else {
454 // Field value is not sensitive, format normally
455 // Push back current item to continue after formatting field value
456 item.state = StackState::ProcessStructField {
457 field_index: field_index + 1,
458 };
459
460 let finish_item = StackItem {
461 peek: *field_value,
462 format_depth: item.format_depth,
463 type_depth: item.type_depth + 1,
464 state: StackState::Finish,
465 };
466 let start_item = StackItem {
467 peek: *field_value,
468 format_depth: item.format_depth,
469 type_depth: item.type_depth + 1,
470 state: StackState::Start,
471 };
472
473 stack.push_back(item);
474 stack.push_back(finish_item);
475 stack.push_back(start_item);
476 }
477 } else if let Peek::Enum(enum_val) = item.peek {
478 // Since PeekEnum implements Copy, we can use it directly
479
480 // Get all fields with their metadata
481 let fields: Vec<_> = enum_val.fields_with_metadata().collect();
482
483 // Check if we're done processing fields
484 if field_index >= fields.len() {
485 // Determine variant kind to use the right closing delimiter
486 match enum_val.variant_kind_active() {
487 VariantKind::Tuple { .. } => {
488 // Close tuple variant with )
489 write!(
490 f,
491 "{:width$}{}",
492 "",
493 self.style_punctuation(")"),
494 width = (item.format_depth - 1) * self.indent_size
495 )?;
496 }
497 VariantKind::Struct { .. } => {
498 // Close struct variant with }
499 write!(
500 f,
501 "{:width$}{}",
502 "",
503 self.style_punctuation("}"),
504 width = (item.format_depth - 1) * self.indent_size
505 )?;
506 }
507 _ => {}
508 }
509 continue;
510 }
511
512 // Get the current field with metadata
513 let (_, field_name, field_peek, field) = fields[field_index];
514
515 // Define consistent indentation
516 let field_indent = " "; // Use 2 spaces for all fields
517
518 // Add field doc comments if available
519 if !field.doc.is_empty() {
520 // Only add new line if not the first field
521 if field_index > 0 {
522 writeln!(f)?;
523 }
524 for line in field.doc {
525 // Hard-code consistent indentation (2 spaces)
526 write!(f, " ")?;
527 self.write_comment(f, &format!("///{}", line))?;
528 writeln!(f)?;
529 }
530 // Rewrite indentation after doc comments
531 write!(f, "{}", field_indent)?;
532 }
533
534 // For struct variants, print field name
535 if let VariantKind::Struct { .. } = enum_val.variant_kind_active() {
536 self.write_field_name(f, field_name)?;
537 self.write_punctuation(f, ": ")?;
538 }
539
540 // Set up to process the next field after this one
541 item.state = StackState::ProcessStructField {
542 field_index: field_index + 1,
543 };
544
545 // Create finish and start items for processing the field value
546 let finish_item = StackItem {
547 peek: field_peek, // field_peek is already a Peek which is Copy
548 format_depth: item.format_depth,
549 type_depth: item.type_depth + 1,
550 state: StackState::Finish,
551 };
552 let start_item = StackItem {
553 peek: field_peek, // field_peek is already a Peek which is Copy
554 format_depth: item.format_depth,
555 type_depth: item.type_depth + 1,
556 state: StackState::Start,
557 };
558
559 // Push items to stack in the right order
560 stack.push_back(item);
561 stack.push_back(finish_item);
562 stack.push_back(start_item);
563 }
564 }
565 StackState::ProcessListItem { item_index } => {
566 if let Peek::List(list) = item.peek {
567 if item_index >= list.len() {
568 // All items processed, write closing bracket
569 write!(
570 f,
571 "{:width$}",
572 "",
573 width = (item.format_depth - 1) * self.indent_size
574 )?;
575 self.write_punctuation(f, "]")?;
576 continue;
577 }
578
579 // Indent
580 write!(
581 f,
582 "{:width$}",
583 "",
584 width = item.format_depth * self.indent_size
585 )?;
586
587 // Push back current item to continue after formatting list item
588 item.state = StackState::ProcessListItem {
589 item_index: item_index + 1,
590 };
591 let next_format_depth = item.format_depth;
592 let next_type_depth = item.type_depth + 1;
593 stack.push_back(item);
594
595 // Push list item to format first
596 let list_item = list.iter().nth(item_index).unwrap();
597 stack.push_back(StackItem {
598 peek: list_item,
599 format_depth: next_format_depth,
600 type_depth: next_type_depth,
601 state: StackState::Finish,
602 });
603
604 // When we push a list item to format, we need to process it from the beginning
605 stack.push_back(StackItem {
606 peek: list_item,
607 format_depth: next_format_depth,
608 type_depth: next_type_depth,
609 state: StackState::Start, // Use Start state to properly process the item
610 });
611 }
612 }
613 StackState::ProcessBytesItem { item_index } => {
614 if let Peek::List(list) = item.peek {
615 if item_index >= list.len() {
616 // All items processed, write closing bracket
617 write!(
618 f,
619 "{:width$}",
620 "",
621 width = (item.format_depth - 1) * self.indent_size
622 )?;
623 continue;
624 }
625
626 // On the first byte, write the opening byte sequence indicator
627 if item_index == 0 {
628 write!(f, " ")?;
629 }
630
631 // Only display 16 bytes per line
632 if item_index > 0 && item_index % 16 == 0 {
633 writeln!(f)?;
634 write!(
635 f,
636 "{:width$}",
637 "",
638 width = item.format_depth * self.indent_size
639 )?;
640 } else if item_index > 0 {
641 write!(f, " ")?;
642 }
643
644 // Get the byte
645 if let Some(Peek::Value(value)) = list.iter().nth(item_index) {
646 let byte = unsafe { value.data().read::<u8>() };
647
648 // Generate a color for this byte based on its value
649 let mut hasher = DefaultHasher::new();
650 byte.hash(&mut hasher);
651 let hash = hasher.finish();
652 let color = self.color_generator.generate_color(hash);
653
654 // Apply color if needed
655 if self.use_colors {
656 write!(f, "\x1b[38;2;{};{};{}m", color.r, color.g, color.b)?;
657 }
658
659 // Display the byte in hex format
660 write!(f, "{:02x}", byte)?;
661
662 // Reset color if needed
663 // Reset color already handled by stylize
664 } else {
665 unreachable!()
666 }
667
668 // Push back current item to continue after formatting byte
669 item.state = StackState::ProcessBytesItem {
670 item_index: item_index + 1,
671 };
672 stack.push_back(item);
673 }
674 }
675 StackState::ProcessMapEntry => {
676 if let Peek::Map(_) = item.peek {
677 // TODO: Implement proper map iteration when available in facet
678
679 // Indent
680 write!(
681 f,
682 "{:width$}",
683 "",
684 width = item.format_depth * self.indent_size
685 )?;
686 write!(f, "{}", self.style_comment("/* Map contents */"))?;
687 writeln!(f)?;
688
689 // Closing brace with proper indentation
690 write!(
691 f,
692 "{:width$}{}",
693 "",
694 self.style_punctuation("}"),
695 width = (item.format_depth - 1) * self.indent_size
696 )?;
697 }
698 }
699 StackState::Finish => {
700 // Add comma and newline for struct fields and list items
701 self.write_punctuation(f, ",")?;
702 writeln!(f)?;
703 }
704 StackState::OptionFinish => {
705 // Just close the Option::Some parenthesis, with no comma
706 self.write_punctuation(f, ")")?;
707 }
708 }
709 }
710
711 Ok(())
712 }
713
714 /// Format a scalar value
715 fn format_value(&self, value: PeekValue, f: &mut impl Write) -> fmt::Result {
716 // Generate a color for this shape
717 let mut hasher = DefaultHasher::new();
718 value.shape().def.hash(&mut hasher);
719 let hash = hasher.finish();
720 let color = self.color_generator.generate_color(hash);
721
722 // Display the value
723 struct DisplayWrapper<'a>(&'a PeekValue<'a>);
724
725 impl fmt::Display for DisplayWrapper<'_> {
726 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
727 if self.0.display(f).is_none() {
728 // If the value doesn't implement Display, use Debug
729 if self.0.debug(f).is_none() {
730 // If the value doesn't implement Debug either, just show the type name
731 self.0.type_name(f, TypeNameOpts::infinite())?;
732 write!(f, "(⋯)")?;
733 }
734 }
735 Ok(())
736 }
737 }
738
739 // Apply color if needed and display
740 if self.use_colors {
741 // We need to use direct ANSI codes for RGB colors
742 write!(
743 f,
744 "\x1b[38;2;{};{};{}m{}",
745 color.r,
746 color.g,
747 color.b,
748 DisplayWrapper(&value)
749 )?;
750 write!(f, "\x1b[0m")?;
751 } else {
752 write!(f, "{}", DisplayWrapper(&value))?;
753 }
754
755 Ok(())
756 }
757
758 /// Write styled type name to formatter
759 fn write_type_name<W: fmt::Write>(&self, f: &mut W, peek: &PeekValue) -> fmt::Result {
760 struct TypeNameWriter<'a, 'b: 'a>(&'b PeekValue<'a>);
761
762 impl core::fmt::Display for TypeNameWriter<'_, '_> {
763 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
764 self.0.type_name(f, TypeNameOpts::infinite())
765 }
766 }
767 let type_name = TypeNameWriter(peek);
768
769 if self.use_colors {
770 write!(f, "{}", type_name.bold())
771 } else {
772 write!(f, "{}", type_name)
773 }
774 }
775
776 /// Style a type name and return it as a string
777 #[allow(dead_code)]
778 fn style_type_name(&self, peek: &PeekValue) -> String {
779 let mut result = String::new();
780 self.write_type_name(&mut result, peek).unwrap();
781 result
782 }
783
784 /// Write styled field name to formatter
785 fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
786 if self.use_colors {
787 // Use cyan color for field names (approximating original RGB color)
788 write!(f, "{}", name.cyan())
789 } else {
790 write!(f, "{}", name)
791 }
792 }
793
794 /// Write styled punctuation to formatter
795 fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
796 if self.use_colors {
797 write!(f, "{}", text.dimmed())
798 } else {
799 write!(f, "{}", text)
800 }
801 }
802
803 /// Style punctuation and return it as a string
804 fn style_punctuation(&self, text: &str) -> String {
805 let mut result = String::new();
806 self.write_punctuation(&mut result, text).unwrap();
807 result
808 }
809
810 /// Write styled comment to formatter
811 fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
812 if self.use_colors {
813 write!(f, "{}", text.dimmed())
814 } else {
815 write!(f, "{}", text)
816 }
817 }
818
819 /// Style a comment and return it as a string
820 fn style_comment(&self, text: &str) -> String {
821 let mut result = String::new();
822 self.write_comment(&mut result, text).unwrap();
823 result
824 }
825
826 /// Write styled redacted value to formatter
827 fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
828 if self.use_colors {
829 // Use bright red and bold for redacted values
830 write!(f, "{}", text.bright_red().bold())
831 } else {
832 write!(f, "{}", text)
833 }
834 }
835
836 /// Style a redacted value and return it as a string
837 #[allow(dead_code)]
838 fn style_redacted(&self, text: &str) -> String {
839 let mut result = String::new();
840 self.write_redacted(&mut result, text).unwrap();
841 result
842 }
843}
844
845#[cfg(test)]
846mod tests {
847 use super::*;
848
849 // Basic tests for the PrettyPrinter
850 #[test]
851 fn test_pretty_printer_default() {
852 let printer = PrettyPrinter::default();
853 assert_eq!(printer.indent_size, 2);
854 assert_eq!(printer.max_depth, None);
855 assert!(printer.use_colors);
856 }
857
858 #[test]
859 fn test_pretty_printer_with_methods() {
860 let printer = PrettyPrinter::new()
861 .with_indent_size(4)
862 .with_max_depth(3)
863 .with_colors(false);
864
865 assert_eq!(printer.indent_size, 4);
866 assert_eq!(printer.max_depth, Some(3));
867 assert!(!printer.use_colors);
868 }
869}