bc_envelope/format/
notation.rs

1//! Formats an envelope as a CBOR data structure and provides human-readable
2//! text representations.
3//!
4//! This module provides functionality for formatting envelopes in various ways:
5//!
6//! 1. **Envelope Notation**: A human-readable text representation showing the
7//!    semantic structure of an envelope (the `format` methods)
8//!
9//! 2. **CBOR Diagnostics**: Text representation of the underlying CBOR structure, following
10//!    [RFC-8949 ยง8](https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation)
11//!    (the `diagnostic` methods)
12//!
13//! 3. **CBOR Hex**: Hexadecimal representation of the raw CBOR bytes (the `hex`
14//!    methods)
15//!
16//! All of these formats can be used for debugging, visualization, and
17//! understanding the structure of envelopes.
18//!
19//! # Examples
20//!
21//! ```
22//! use bc_envelope::prelude::*;
23//!
24//! // Create a simple envelope with assertions
25//! let envelope = Envelope::new("Alice")
26//!     .add_assertion("knows", "Bob")
27//!     .add_assertion("knows", "Carol");
28//!
29//! // Format the envelope as human-readable envelope notation
30//! let formatted = envelope.format();
31//! // Will output: "Alice" [ "knows": "Bob", "knows": "Carol" ]
32//!
33//! // Get the CBOR diagnostic notation (with annotations)
34//! let diagnostic = envelope.diagnostic_annotated();
35//!
36//! // Get the CBOR hex representation
37//! let hex = envelope.hex();
38//! ```
39
40use bc_components::XID;
41use dcbor::prelude::*;
42
43use super::EnvelopeSummary;
44use crate::{
45    Assertion, Envelope, FormatContextOpt,
46    base::envelope::EnvelopeCase, string_utils::StringUtils,
47};
48#[cfg(feature = "known_value")]
49use crate::{KnownValue, known_values};
50
51#[derive(Clone, Default)]
52pub struct EnvelopeFormatOpts<'a> {
53    flat: bool,
54    context: FormatContextOpt<'a>,
55}
56
57impl<'a> EnvelopeFormatOpts<'a> {
58    /// Sets the flat format option.
59    pub fn flat(mut self, flat: bool) -> Self {
60        self.flat = flat;
61        self
62    }
63
64    /// Sets the format context option.
65    pub fn context(mut self, context: FormatContextOpt<'a>) -> Self {
66        self.context = context;
67        self
68    }
69}
70
71/// Support for the various text output formats for ``Envelope``.
72impl Envelope {
73    /// Returns the envelope notation for this envelope.
74    pub fn format_opt<'a>(&self, opts: &EnvelopeFormatOpts<'a>) -> String {
75        self.format_item(opts)
76            .format(opts)
77            .trim()
78            .to_string()
79    }
80
81    /// Returns the envelope notation for this envelope.
82    ///
83    /// Uses the current format context.
84    pub fn format(&self) -> String {
85        self.format_opt(&EnvelopeFormatOpts::default())
86    }
87
88    /// Returns the envelope notation for this envelope in flat format.
89    ///
90    /// In flat format, the envelope is printed on a single line.
91    pub fn format_flat(&self) -> String {
92        self.format_opt(&EnvelopeFormatOpts::default().flat(true))
93    }
94}
95
96/// Implementers of this trait define how to be formatted in when output in
97/// envelope notation.
98pub trait EnvelopeFormat {
99    fn format_item<'a>(
100        &self,
101        opts: &EnvelopeFormatOpts<'a>,
102    ) -> EnvelopeFormatItem;
103}
104
105/// This type is returned by implementers of the [`EnvelopeFormat`] trait.
106#[derive(Debug, Clone, Eq)]
107pub enum EnvelopeFormatItem {
108    Begin(String),
109    End(String),
110    Item(String),
111    Separator,
112    List(Vec<EnvelopeFormatItem>),
113}
114
115impl EnvelopeFormatItem {
116    fn flatten(&self) -> Vec<EnvelopeFormatItem> {
117        match self {
118            EnvelopeFormatItem::List(items) => {
119                items.iter().flat_map(|i| i.flatten()).collect()
120            }
121            _ => vec![self.clone()],
122        }
123    }
124
125    fn nicen(items: &[EnvelopeFormatItem]) -> Vec<EnvelopeFormatItem> {
126        let mut input = items.to_vec();
127        let mut result: Vec<EnvelopeFormatItem> = vec![];
128
129        while !input.is_empty() {
130            let current = input.remove(0);
131            if input.is_empty() {
132                result.push(current);
133                break;
134            }
135            if let EnvelopeFormatItem::End(end_string) = current.clone() {
136                if let EnvelopeFormatItem::Begin(begin_string) =
137                    input[0].clone()
138                {
139                    result.push(EnvelopeFormatItem::End(format!(
140                        "{} {}",
141                        end_string, begin_string
142                    )));
143                    result.push(EnvelopeFormatItem::Begin("".to_string()));
144                    input.remove(0);
145                } else {
146                    result.push(current);
147                }
148            } else {
149                result.push(current);
150            }
151        }
152
153        result
154    }
155
156    fn indent(level: usize) -> String { " ".repeat(level * 4) }
157
158    fn add_space_at_end_if_needed(s: &str) -> String {
159        if s.is_empty() {
160            " ".to_string()
161        } else if s.ends_with(' ') {
162            s.to_string()
163        } else {
164            s.to_string() + " "
165        }
166    }
167
168    fn format(&self, opts: &EnvelopeFormatOpts<'_>) -> String {
169        if opts.flat {
170            return self.format_flat();
171        }
172        self.format_hierarchical()
173    }
174
175    fn format_flat(&self) -> String {
176        let mut line: String = "".to_string();
177        let items = self.flatten();
178        for item in items {
179            match item {
180                EnvelopeFormatItem::Begin(s) => {
181                    if !line.ends_with(' ') {
182                        line += " ";
183                    }
184                    line += &s;
185                    line += " ";
186                }
187                EnvelopeFormatItem::End(s) => {
188                    if !line.ends_with(' ') {
189                        line += " ";
190                    }
191                    line += &s;
192                    line += " ";
193                }
194                EnvelopeFormatItem::Item(s) => line += &s,
195                EnvelopeFormatItem::Separator => {
196                    line = line.trim_end().to_string() + ", ";
197                }
198                EnvelopeFormatItem::List(items) => {
199                    for item in items {
200                        line += &item.format_flat();
201                    }
202                }
203            }
204        }
205        line
206    }
207
208    fn format_hierarchical(&self) -> String {
209        let mut lines: Vec<String> = vec![];
210        let mut level = 0;
211        let mut current_line = "".to_string();
212        let items = Self::nicen(&self.flatten());
213        for item in items {
214            match item {
215                EnvelopeFormatItem::Begin(delimiter) => {
216                    if !delimiter.is_empty() {
217                        let c = if current_line.is_empty() {
218                            delimiter
219                        } else {
220                            Self::add_space_at_end_if_needed(&current_line)
221                                + &delimiter
222                        };
223                        lines.push(Self::indent(level) + &c + "\n");
224                    }
225                    level += 1;
226                    current_line = "".to_string();
227                }
228                EnvelopeFormatItem::End(delimiter) => {
229                    if !current_line.is_empty() {
230                        lines.push(Self::indent(level) + &current_line + "\n");
231                        current_line = "".to_string();
232                    }
233                    level -= 1;
234                    lines.push(Self::indent(level) + &delimiter + "\n");
235                }
236                EnvelopeFormatItem::Item(string) => {
237                    current_line += &string;
238                }
239                EnvelopeFormatItem::Separator => {
240                    if !current_line.is_empty() {
241                        lines.push(Self::indent(level) + &current_line + "\n");
242                        current_line = "".to_string();
243                    }
244                }
245                EnvelopeFormatItem::List(_) => {
246                    lines.push("<list>".to_string());
247                }
248            }
249        }
250        if !current_line.is_empty() {
251            lines.push(current_line);
252        }
253        lines.join("")
254    }
255}
256
257/// Implements conversion from string slice to EnvelopeFormatItem.
258impl From<&str> for EnvelopeFormatItem {
259    fn from(s: &str) -> Self { Self::Item(s.to_string()) }
260}
261
262/// Implements string display for EnvelopeFormatItem.
263impl std::fmt::Display for EnvelopeFormatItem {
264    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265        match self {
266            EnvelopeFormatItem::Begin(s) => write!(f, ".begin({})", s),
267            EnvelopeFormatItem::End(s) => write!(f, ".end({})", s),
268            EnvelopeFormatItem::Item(s) => write!(f, ".item({})", s),
269            EnvelopeFormatItem::Separator => write!(f, ".separator"),
270            EnvelopeFormatItem::List(items) => write!(f, ".list({:?})", items),
271        }
272    }
273}
274
275/// Implements partial equality for EnvelopeFormatItem.
276impl PartialEq for EnvelopeFormatItem {
277    fn eq(&self, other: &Self) -> bool {
278        match (self, other) {
279            (EnvelopeFormatItem::Begin(s1), EnvelopeFormatItem::Begin(s2)) => {
280                s1 == s2
281            }
282            (EnvelopeFormatItem::End(s1), EnvelopeFormatItem::End(s2)) => {
283                s1 == s2
284            }
285            (EnvelopeFormatItem::Item(s1), EnvelopeFormatItem::Item(s2)) => {
286                s1 == s2
287            }
288            (EnvelopeFormatItem::Separator, EnvelopeFormatItem::Separator) => {
289                true
290            }
291            (
292                EnvelopeFormatItem::List(items1),
293                EnvelopeFormatItem::List(items2),
294            ) => items1 == items2,
295            _ => false,
296        }
297    }
298}
299
300impl EnvelopeFormatItem {
301    fn index(&self) -> u32 {
302        match self {
303            EnvelopeFormatItem::Begin(_) => 1,
304            EnvelopeFormatItem::End(_) => 2,
305            EnvelopeFormatItem::Item(_) => 3,
306            EnvelopeFormatItem::Separator => 4,
307            EnvelopeFormatItem::List(_) => 5,
308        }
309    }
310}
311
312/// Implements partial ordering for EnvelopeFormatItem.
313impl PartialOrd for EnvelopeFormatItem {
314    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
315        Some(self.cmp(other))
316    }
317}
318
319/// Implements total ordering for EnvelopeFormatItem.
320impl Ord for EnvelopeFormatItem {
321    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
322        let l_index = self.index();
323        let r_index = other.index();
324        match l_index.cmp(&r_index) {
325            std::cmp::Ordering::Less => return std::cmp::Ordering::Less,
326            std::cmp::Ordering::Greater => return std::cmp::Ordering::Greater,
327            _ => {}
328        }
329        match (self, other) {
330            (EnvelopeFormatItem::Begin(l), EnvelopeFormatItem::Begin(r)) => {
331                l.cmp(r)
332            }
333            (EnvelopeFormatItem::End(l), EnvelopeFormatItem::End(r)) => {
334                l.cmp(r)
335            }
336            (EnvelopeFormatItem::Item(l), EnvelopeFormatItem::Item(r)) => {
337                l.cmp(r)
338            }
339            (EnvelopeFormatItem::Separator, EnvelopeFormatItem::Separator) => {
340                std::cmp::Ordering::Equal
341            }
342            (EnvelopeFormatItem::List(l), EnvelopeFormatItem::List(r)) => {
343                l.cmp(r)
344            }
345            _ => std::cmp::Ordering::Equal,
346        }
347    }
348}
349
350/// Implements formatting for CBOR values in envelope notation.
351impl EnvelopeFormat for CBOR {
352    fn format_item<'a>(
353        &self,
354        opts: &EnvelopeFormatOpts<'a>,
355    ) -> EnvelopeFormatItem {
356        match self.as_case() {
357            CBORCase::Tagged(tag, cbor) if tag == &Envelope::cbor_tags()[0] => {
358                Envelope::from_untagged_cbor(cbor.clone())
359                    .map(|envelope| envelope.format_item(opts))
360                    .unwrap_or_else(|_| "<error>".into())
361            }
362            _ => EnvelopeFormatItem::Item(
363                self.envelope_summary(usize::MAX, &opts.context)
364                    .unwrap_or_else(|_| "<error>".into()),
365            ),
366        }
367    }
368}
369
370/// Implements formatting for Envelope in envelope notation.
371impl EnvelopeFormat for Envelope {
372    fn format_item<'a>(
373        &self,
374        opts: &EnvelopeFormatOpts<'a>,
375    ) -> EnvelopeFormatItem {
376        match self.case() {
377            EnvelopeCase::Leaf { cbor, .. } => cbor.format_item(opts),
378            EnvelopeCase::Wrapped { envelope, .. } => {
379                EnvelopeFormatItem::List(vec![
380                    EnvelopeFormatItem::Begin("{".to_string()),
381                    envelope.format_item(opts),
382                    EnvelopeFormatItem::End("}".to_string()),
383                ])
384            }
385            EnvelopeCase::Assertion(assertion) => {
386                assertion.format_item(opts)
387            }
388            #[cfg(feature = "known_value")]
389            EnvelopeCase::KnownValue { value, .. } => {
390                value.format_item(opts)
391            }
392            #[cfg(feature = "encrypt")]
393            EnvelopeCase::Encrypted(_) => {
394                EnvelopeFormatItem::Item("ENCRYPTED".to_string())
395            }
396            #[cfg(feature = "compress")]
397            EnvelopeCase::Compressed(_) => {
398                EnvelopeFormatItem::Item("COMPRESSED".to_string())
399            }
400            EnvelopeCase::Node { subject, assertions, .. } => {
401                let mut items: Vec<EnvelopeFormatItem> = Vec::new();
402
403                let subject_item = subject.format_item(opts);
404                let mut elided_count = 0;
405                #[cfg(feature = "encrypt")]
406                let mut encrypted_count = 0;
407                #[cfg(feature = "compress")]
408                let mut compressed_count = 0;
409                #[cfg(feature = "known_value")]
410                let mut type_assertion_items: Vec<
411                    Vec<EnvelopeFormatItem>,
412                > = Vec::new();
413                let mut assertion_items: Vec<Vec<EnvelopeFormatItem>> =
414                    Vec::new();
415
416                for assertion in assertions {
417                    match assertion.case() {
418                        EnvelopeCase::Elided(_) => {
419                            elided_count += 1;
420                        }
421                        #[cfg(feature = "encrypt")]
422                        EnvelopeCase::Encrypted(_) => {
423                            encrypted_count += 1;
424                        }
425                        #[cfg(feature = "compress")]
426                        EnvelopeCase::Compressed(_) => {
427                            compressed_count += 1;
428                        }
429                        _ => {
430                            let item =
431                                vec![assertion.format_item(opts)];
432                            #[cfg(feature = "known_value")]
433                            {
434                                let mut is_type_assertion = false;
435                                if let Some(predicate) =
436                                    assertion.as_predicate()
437                                {
438                                    if let Some(known_value) =
439                                        predicate.subject().as_known_value()
440                                    {
441                                        if *known_value == known_values::IS_A {
442                                            is_type_assertion = true;
443                                        }
444                                    }
445                                }
446                                if is_type_assertion {
447                                    type_assertion_items.push(item);
448                                } else {
449                                    assertion_items.push(item);
450                                }
451                            }
452                            #[cfg(not(feature = "known_value"))]
453                            assertion_items.push(item);
454                        }
455                    }
456                }
457                #[cfg(feature = "known_value")]
458                type_assertion_items.sort();
459                assertion_items.sort();
460                #[cfg(feature = "known_value")]
461                assertion_items.splice(0..0, type_assertion_items);
462                #[cfg(feature = "compress")]
463                if compressed_count > 1 {
464                    assertion_items.push(vec![EnvelopeFormatItem::Item(
465                        format!("COMPRESSED ({})", compressed_count),
466                    )]);
467                } else if compressed_count > 0 {
468                    assertion_items.push(vec![EnvelopeFormatItem::Item(
469                        "COMPRESSED".to_string(),
470                    )]);
471                }
472                if elided_count > 1 {
473                    assertion_items.push(vec![EnvelopeFormatItem::Item(
474                        format!("ELIDED ({})", elided_count),
475                    )]);
476                } else if elided_count > 0 {
477                    assertion_items.push(vec![EnvelopeFormatItem::Item(
478                        "ELIDED".to_string(),
479                    )]);
480                }
481                #[cfg(feature = "encrypt")]
482                if encrypted_count > 1 {
483                    assertion_items.push(vec![EnvelopeFormatItem::Item(
484                        format!("ENCRYPTED ({})", encrypted_count),
485                    )]);
486                } else if encrypted_count > 0 {
487                    assertion_items.push(vec![EnvelopeFormatItem::Item(
488                        "ENCRYPTED".to_string(),
489                    )]);
490                }
491                let joined_assertions_items: Vec<Vec<EnvelopeFormatItem>> =
492                    itertools::intersperse_with(assertion_items, || {
493                        vec![EnvelopeFormatItem::Separator]
494                    })
495                    .collect();
496
497                let needs_braces = subject.is_subject_assertion();
498
499                if needs_braces {
500                    items.push(EnvelopeFormatItem::Begin("{".to_string()));
501                }
502                items.push(subject_item);
503                if needs_braces {
504                    items.push(EnvelopeFormatItem::End("}".to_string()));
505                }
506                items.push(EnvelopeFormatItem::Begin("[".to_string()));
507                items.extend(joined_assertions_items.into_iter().flatten());
508                items.push(EnvelopeFormatItem::End("]".to_string()));
509                EnvelopeFormatItem::List(items)
510            }
511            EnvelopeCase::Elided(_) => {
512                EnvelopeFormatItem::Item("ELIDED".to_string())
513            }
514        }
515    }
516}
517
518/// Implements formatting for Assertion in envelope notation.
519impl EnvelopeFormat for Assertion {
520    fn format_item<'a>(
521        &self,
522        opts: &EnvelopeFormatOpts<'a>,
523    ) -> EnvelopeFormatItem {
524        EnvelopeFormatItem::List(vec![
525            self.predicate().format_item(opts),
526            EnvelopeFormatItem::Item(": ".to_string()),
527            self.object().format_item(opts),
528        ])
529    }
530}
531
532#[cfg(feature = "known_value")]
533/// Implements formatting for KnownValue in envelope notation.
534impl EnvelopeFormat for KnownValue {
535    fn format_item<'a>(
536        &self,
537        opts: &EnvelopeFormatOpts<'a>,
538    ) -> EnvelopeFormatItem {
539        let name = match opts.context {
540            FormatContextOpt::None => {
541                self.name().to_string().flanked_by("'", "'")
542            }
543            FormatContextOpt::Global => {
544                crate::with_format_context!(|context: &crate::FormatContext| {
545                    context
546                        .known_values()
547                        .assigned_name(self)
548                        .map(|s| s.to_string())
549                        .unwrap_or_else(|| self.name().to_string())
550                })
551            }
552            FormatContextOpt::Custom(format_context) => {
553                    format_context
554                        .known_values()
555                        .assigned_name(self)
556                        .map(|s| s.to_string())
557                        .unwrap_or_else(|| self.name().to_string())
558            }
559        };
560        EnvelopeFormatItem::Item(name.flanked_by("'", "'"))
561    }
562}
563
564/// Implements formatting for XID in envelope notation.
565impl EnvelopeFormat for XID {
566    fn format_item<'a>(
567        &self,
568        _opts: &EnvelopeFormatOpts<'a>,
569    ) -> EnvelopeFormatItem {
570        EnvelopeFormatItem::Item(hex::encode(self.data()))
571    }
572}
573
574impl Envelope {
575    fn description<'a>(&self, opts: &EnvelopeFormatOpts<'a>) -> String {
576        match self.case() {
577            EnvelopeCase::Node { subject, assertions, .. } => {
578                let assertions = assertions
579                    .iter()
580                    .map(|a| a.to_string())
581                    .collect::<Vec<String>>()
582                    .join(", ")
583                    .flanked_by("[", "]");
584                format!(".node({}, {})", subject, assertions)
585            }
586            EnvelopeCase::Leaf { cbor, .. } => {
587                format!(
588                            ".cbor({})",
589                            cbor.format_item(opts)
590                        )
591            },
592            EnvelopeCase::Wrapped { envelope, .. } => {
593                format!(".wrapped({})", envelope)
594            }
595            EnvelopeCase::Assertion(assertion) => format!(
596                ".assertion({}, {})",
597                assertion.predicate(),
598                assertion.object()
599            ),
600            EnvelopeCase::Elided(_) => ".elided".to_string(),
601            #[cfg(feature = "known_value")]
602            EnvelopeCase::KnownValue { value, .. } => {
603                format!(".knownValue({})", value)
604            }
605            #[cfg(feature = "encrypt")]
606            EnvelopeCase::Encrypted(_) => ".encrypted".to_string(),
607            #[cfg(feature = "compress")]
608            EnvelopeCase::Compressed(_) => ".compressed".to_string(),
609        }
610    }
611}
612
613/// Implements string display for Envelope.
614impl std::fmt::Display for Envelope {
615    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616        f.write_str(&self.description(&EnvelopeFormatOpts::default()))
617    }
618}