tree_display/context.rs
1//! Custom value transformations for tree display.
2//!
3//! Register mappers to modify how values are displayed without changing
4//! the underlying data. Useful for formatting, redaction, or debugging
5//! transformations.
6
7use super::format::Content;
8use std::{
9 any::{Any, TypeId},
10 collections::HashMap,
11};
12
13// ──── API ───────────────────────────────────────────────────────────────────────────────────────
14
15/// A function that maps one content value to another.
16pub type Mapper = dyn Fn(&dyn Content) -> Box<dyn Content>;
17
18/// Context for custom value transformations.
19///
20/// A context holds a collection of mappers keyed by the [`TypeId`] of the
21/// input type. When displaying a tree, the context is used to apply
22/// transformations to values before they are rendered.
23///
24/// ## Example
25/// ```no_run
26/// use tree_display::Context;
27///
28/// let context = Context::new()
29/// .map(|s: &String| format!("'{}'", s))
30/// .map(|n: &i32| format!("{}", n * 2));
31/// ```
32#[derive(Default)]
33pub struct Context {
34 /// Map from [`TypeId`] to a mapper function for that type.
35 pub mappers: HashMap<TypeId, Box<Mapper>>,
36}
37
38impl Context {
39 /// Creates a new empty context with no mappers.
40 pub fn new() -> Self {
41 Self {
42 mappers: HashMap::new(),
43 }
44 }
45
46 /// Registers a mapper for a specific type.
47 ///
48 /// The mapper function takes a reference to a value and returns another
49 /// value that implements `Content`. The transformed value will
50 /// be used when displaying values of type `T` in the tree.
51 ///
52 /// To apply a mapper to a specific field in a struct or enum, use the
53 /// `#[tree(map)]` attribute on that field when deriving [`TreeDisplay`](`super::TreeDisplay`).
54 ///
55 /// ## Type Parameters
56 /// - `T`: The input type to map from
57 /// - `R`: The output type that implements [`Content`]
58 ///
59 /// ## Example
60 /// ```no_run
61 /// use tree_display::context::Context;
62 ///
63 /// let context = Context::new()
64 /// .map(|s: &String| format!("'{}'", s))
65 /// .map(|n: &i32| format!("{}", n * 2));
66 /// ```
67 pub fn map<T: 'static, R: Content + 'static>(mut self, f: impl Fn(&T) -> R + 'static) -> Self {
68 self.mappers.insert(
69 TypeId::of::<T>(),
70 Box::new(move |content| {
71 let t = (content as &dyn Any)
72 .downcast_ref::<T>()
73 .expect("TypeId guarantees this matches");
74 Box::new(f(t))
75 }),
76 );
77 self
78 }
79}