bc_envelope/base/tree_format.rs
1//! Creates a textual tree representation of an envelope for debugging and visualization.
2//!
3//! This module provides functionality for creating a textual tree representation of an envelope,
4//! which is useful for debugging and visualizing the structure of complex envelopes.
5//!
6//! The tree format displays each component of an envelope (subject and assertions) as nodes in a tree,
7//! making it easy to understand the hierarchical structure of nested envelopes. Each node includes:
8//!
9//! * The first 8 characters of the element's digest (for easy reference)
10//! * The type of the element (NODE, ASSERTION, ELIDED, etc.)
11//! * The content of the element (for leaf nodes)
12//!
13//! # Examples
14//!
15//! ```
16//! use bc_envelope::prelude::*;
17//!
18//! // Create a complex envelope with nested assertions
19//! let envelope = Envelope::new("Alice")
20//! .add_assertion("knows", Envelope::new("Bob")
21//! .add_assertion("email", "bob@example.com"));
22//!
23//! // Get a tree representation of the envelope
24//! let tree = envelope.tree_format(false);
25//! // Output will look like:
26//! // 9e3b0673 NODE
27//! // 13941b48 subj "Alice"
28//! // f45afd77 ASSERTION
29//! // db7dd21c pred "knows"
30//! // 76543210 obj NODE
31//! // 13b74194 subj "Bob"
32//! // ee23dcba ASSERTION
33//! // a9e85a47 pred "email"
34//! // 84fd6e57 obj "bob@example.com"
35//! ```
36
37use std::{collections::HashSet, cell::RefCell};
38
39use bc_components::{Digest, DigestProvider};
40
41use crate::{Envelope, with_format_context, FormatContext};
42#[cfg(feature = "known_value")]
43use crate::{string_utils::StringUtils, extension::KnownValuesStore};
44
45use super::{walk::EdgeType, EnvelopeSummary, envelope::EnvelopeCase};
46
47/// Support for tree-formatting envelopes.
48impl Envelope {
49 /// Returns a tree-formatted string representation of the envelope with optional context.
50 ///
51 /// # Arguments
52 /// * `hide_nodes` - If true, hides NODE identifiers and only shows the semantic content
53 /// * `context` - Optional formatting context
54 pub fn tree_format_opt(&self, hide_nodes: bool, context: Option<&FormatContext>) -> String {
55 self.tree_format_with_target_opt(hide_nodes, &HashSet::new(), context)
56 }
57
58 /// Returns a tree-formatted string representation of the envelope.
59 ///
60 /// # Arguments
61 /// * `hide_nodes` - If true, hides NODE identifiers and only shows the semantic content
62 pub fn tree_format(&self, hide_nodes: bool) -> String {
63 with_format_context!(|context| {
64 self.tree_format_opt(hide_nodes, Some(context))
65 })
66 }
67
68 /// Returns a tree-formatted string representation of the envelope with highlighted digests.
69 ///
70 /// # Arguments
71 /// * `hide_nodes` - If true, hides NODE identifiers and only shows the semantic content
72 /// * `highlighting_target` - Set of digests to highlight in the tree representation
73 /// * `context` - Optional formatting context
74 pub fn tree_format_with_target_opt(&self, hide_nodes: bool, highlighting_target: &HashSet<Digest>, context: Option<&FormatContext>) -> String {
75 let elements: RefCell<Vec<TreeElement>> = RefCell::new(Vec::new());
76 let visitor = |envelope: Self, level: usize, incoming_edge: EdgeType, _: Option<&()>| -> _ {
77 let elem = TreeElement::new(
78 level,
79 envelope.clone(),
80 incoming_edge,
81 !hide_nodes,
82 highlighting_target.contains(&envelope.digest()),
83 );
84 elements.borrow_mut().push(elem);
85 None
86 };
87 let s = self.clone();
88 s.walk(hide_nodes, &visitor);
89 let elements = elements.borrow();
90 elements.iter().map(|e| e.string(context.unwrap_or(&FormatContext::default()))).collect::<Vec<_>>().join("\n")
91 }
92
93 /// Returns a tree-formatted string representation of the envelope with highlighted digests.
94 ///
95 /// # Arguments
96 /// * `hide_nodes` - If true, hides NODE identifiers and only shows the semantic content
97 /// * `highlighting_target` - Set of digests to highlight in the tree representation
98 pub fn tree_format_with_target(&self, hide_nodes: bool, highlighting_target: &HashSet<Digest>) -> String {
99 with_format_context!(|context| {
100 self.tree_format_with_target_opt(hide_nodes, highlighting_target, Some(context))
101 })
102 }
103}
104
105impl Envelope {
106 /// Returns a shortened hexadecimal representation of the envelope's digest.
107 pub fn short_id(&self) -> String {
108 self.digest().short_description()
109 }
110
111 /// Returns a short summary of the envelope's content with a maximum length.
112 ///
113 /// # Arguments
114 /// * `max_length` - The maximum length of the summary
115 /// * `context` - The formatting context
116 pub fn summary(&self, max_length: usize, context: &FormatContext) -> String {
117 match self.case() {
118 EnvelopeCase::Node { .. } => "NODE".to_string(),
119 EnvelopeCase::Leaf { cbor, .. } => cbor.envelope_summary(max_length, context).unwrap(),
120 EnvelopeCase::Wrapped { .. } => "WRAPPED".to_string(),
121 EnvelopeCase::Assertion(_) => "ASSERTION".to_string(),
122 EnvelopeCase::Elided(_) => "ELIDED".to_string(),
123 #[cfg(feature = "known_value")]
124 EnvelopeCase::KnownValue { value, .. } => {
125 let known_value = KnownValuesStore::known_value_for_raw_value(value.value(), Some(context.known_values()));
126 known_value.to_string().flanked_by("'", "'",)
127 },
128 #[cfg(feature = "encrypt")]
129 EnvelopeCase::Encrypted(_) => "ENCRYPTED".to_string(),
130 #[cfg(feature = "compress")]
131 EnvelopeCase::Compressed(_) => "COMPRESSED".to_string(),
132 }
133 }
134}
135
136/// Represents an element in the tree representation of an envelope.
137#[derive(Debug)]
138struct TreeElement {
139 /// Indentation level of the element in the tree
140 level: usize,
141 /// The envelope element
142 envelope: Envelope,
143 /// The type of edge connecting this element to its parent
144 incoming_edge: EdgeType,
145 /// Whether to show the element's ID (digest)
146 show_id: bool,
147 /// Whether this element should be highlighted in the output
148 is_highlighted: bool,
149}
150
151impl TreeElement {
152 /// Creates a new TreeElement.
153 ///
154 /// # Arguments
155 /// * `level` - Indentation level of the element in the tree
156 /// * `envelope` - The envelope element
157 /// * `incoming_edge` - The type of edge connecting this element to its parent
158 /// * `show_id` - Whether to show the element's ID (digest)
159 /// * `is_highlighted` - Whether this element should be highlighted in the output
160 fn new(level: usize, envelope: Envelope, incoming_edge: EdgeType, show_id: bool, is_highlighted: bool) -> Self {
161 Self { level, envelope, incoming_edge, show_id, is_highlighted }
162 }
163
164 /// Formats the tree element as a string.
165 ///
166 /// # Arguments
167 /// * `context` - The formatting context
168 fn string(&self, context: &FormatContext) -> String {
169 let line = vec![
170 if self.is_highlighted { Some("*".to_string()) } else { None },
171 if self.show_id { Some(self.envelope.short_id()) } else { None },
172 self.incoming_edge.label().map(|s| s.to_string()),
173 Some(self.envelope.summary(40, context)),
174 ].into_iter().flatten().collect::<Vec<_>>().join(" ");
175 let indent = " ".repeat(self.level * 4);
176 format!("{}{}", indent, line)
177 }
178}