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