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