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}