bc_envelope/base/
format_context.rs

1use bc_components::tags::*;
2#[cfg(any(feature = "expression", feature = "known_value"))]
3use std::sync::Arc;
4use std::sync::{ Mutex, Once };
5#[cfg(feature = "known_value")]
6use known_values::{ KnownValuesStore, KnownValue, KNOWN_VALUES };
7#[cfg(feature = "known_value")]
8use bc_components::tags::TAG_KNOWN_VALUE;
9
10#[cfg(feature = "expression")]
11use crate::extension::expressions::{
12    FunctionsStore,
13    ParametersStore,
14    GLOBAL_FUNCTIONS,
15    GLOBAL_PARAMETERS,
16};
17
18#[cfg(feature = "expression")]
19use crate::Envelope;
20
21#[cfg(any(feature = "expression", feature = "known_value"))]
22use crate::string_utils::StringUtils;
23
24#[derive(Clone)]
25pub enum FormatContextOpt {
26    None,
27    Global,
28    Custom(&'static FormatContext),
29}
30
31impl Default for FormatContextOpt {
32    fn default() -> Self {
33        FormatContextOpt::Global
34    }
35}
36
37/// Context object for formatting Gordian Envelopes with annotations.
38///
39/// The `FormatContext` provides information about CBOR tags, known values, functions,
40/// and parameters that are used to annotate the output of envelope formatting functions.
41/// This context enables human-readable output when converting envelopes to string
42/// representations like diagnostic notation.
43///
44/// This type is central to the diagnostic capabilities of Gordian Envelope,
45/// translating numeric CBOR tags into meaningful names and providing context-specific
46/// formatting for special values.
47///
48/// # Format Context Content
49///
50/// A `FormatContext` contains:
51/// - CBOR tag registry (always present)
52/// - Known Values store (when `known_value` feature is enabled)
53/// - Functions store (when `expression` feature is enabled)
54/// - Parameters store (when `expression` feature is enabled)
55/// - A flag indicating whether the format should be flat or structured
56///
57/// # Global Context
58///
59/// A global format context is available through the `with_format_context!` and
60/// `with_format_context_mut!` macros. This global context is initialized with
61/// standard tags and registries.
62///
63/// # Example
64///
65/// Using the global format context to produce annotated CBOR diagnostic notation:
66///
67/// ```
68/// # use bc_envelope::prelude::*;
69/// # use indoc::indoc;
70/// # let e = Envelope::new("Hello.");
71/// # bc_envelope::register_tags();
72/// assert_eq!(e.diagnostic_annotated(),
73/// indoc! {r#"
74/// 200(   / envelope /
75///     201("Hello.")   / leaf /
76/// )
77/// "#}.trim()
78/// );
79/// ```
80///
81/// The annotations (comments after the `/` characters) provide human-readable context
82/// for the CBOR tags and structure.
83#[derive(Clone)]
84pub struct FormatContext {
85    flat: bool,
86    tags: TagsStore,
87    #[cfg(feature = "known_value")]
88    known_values: KnownValuesStore,
89    #[cfg(feature = "expression")]
90    functions: FunctionsStore,
91    #[cfg(feature = "expression")]
92    parameters: ParametersStore,
93}
94
95impl FormatContext {
96    /// Creates a new format context with the specified components.
97    ///
98    /// This constructor allows full customization of the format context by providing
99    /// optional components. Any component not provided will be initialized with its default.
100    ///
101    /// # Parameters
102    ///
103    /// * `flat` - If true, formatting will be flattened without indentation and structure
104    /// * `tags` - Optional CBOR tag registry for mapping tag numbers to names
105    /// * `known_values` - Optional known values registry (requires `known_value` feature)
106    /// * `functions` - Optional functions registry (requires `expression` feature)
107    /// * `parameters` - Optional parameters registry (requires `expression` feature)
108    ///
109    /// # Returns
110    ///
111    /// A new `FormatContext` instance initialized with the provided components.
112    pub fn new(
113        flat: bool,
114        tags: Option<&TagsStore>,
115        #[cfg(feature = "known_value")] known_values: Option<&KnownValuesStore>,
116        #[cfg(feature = "expression")] functions: Option<&FunctionsStore>,
117        #[cfg(feature = "expression")] parameters: Option<&ParametersStore>
118    ) -> Self {
119        Self {
120            flat,
121            tags: tags.cloned().unwrap_or_default(),
122            #[cfg(feature = "known_value")]
123            known_values: known_values.cloned().unwrap_or_default(),
124            #[cfg(feature = "expression")]
125            functions: functions.cloned().unwrap_or_default(),
126            #[cfg(feature = "expression")]
127            parameters: parameters.cloned().unwrap_or_default(),
128        }
129    }
130
131    /// Returns whether flat formatting is enabled.
132    ///
133    /// When flat formatting is enabled, envelope formatting functions produce
134    /// more compact output without indentation and structural formatting.
135    pub fn is_flat(&self) -> bool {
136        self.flat
137    }
138
139    /// Sets whether flat formatting should be enabled and returns the modified context.
140    ///
141    /// This method allows fluent-style modification of the context's flat formatting setting.
142    ///
143    /// # Parameters
144    ///
145    /// * `flat` - If true, flat formatting will be enabled
146    ///
147    /// # Returns
148    ///
149    /// A new `FormatContext` with the updated flat setting
150    pub fn set_flat(mut self, flat: bool) -> Self {
151        self.flat = flat;
152        self
153    }
154
155    /// Returns a reference to the CBOR tags registry.
156    ///
157    /// The tags registry maps CBOR tag numbers to human-readable names
158    /// and provides summarizers for tag-specific formatting.
159    pub fn tags(&self) -> &TagsStore {
160        &self.tags
161    }
162
163    /// Returns a mutable reference to the CBOR tags registry.
164    ///
165    /// This allows modifying the tags registry to add or change tag mappings.
166    pub fn tags_mut(&mut self) -> &mut TagsStore {
167        &mut self.tags
168    }
169
170    /// Returns a reference to the known values registry.
171    ///
172    /// The known values registry maps symbolic values (like "true", "false", etc.)
173    /// to their canonical string representations.
174    ///
175    /// This method is only available when the `known_value` feature is enabled.
176    #[cfg(feature = "known_value")]
177    pub fn known_values(&self) -> &KnownValuesStore {
178        &self.known_values
179    }
180
181    /// Returns a reference to the functions registry.
182    ///
183    /// The functions registry maps function identifiers to their human-readable names
184    /// for use in expression formatting.
185    ///
186    /// This method is only available when the `expression` feature is enabled.
187    #[cfg(feature = "expression")]
188    pub fn functions(&self) -> &FunctionsStore {
189        &self.functions
190    }
191
192    /// Returns a reference to the parameters registry.
193    ///
194    /// The parameters registry maps parameter identifiers to their human-readable names
195    /// for use in expression formatting.
196    ///
197    /// This method is only available when the `expression` feature is enabled.
198    #[cfg(feature = "expression")]
199    pub fn parameters(&self) -> &ParametersStore {
200        &self.parameters
201    }
202}
203
204/// Implementation of `TagsStoreTrait` for `FormatContext`, delegating to the internal `TagsStore`.
205///
206/// This implementation allows a `FormatContext` to be used anywhere a `TagsStoreTrait`
207/// is required, providing the tag resolution functionality directly.
208impl TagsStoreTrait for FormatContext {
209    /// Returns the assigned name for a tag if one exists.
210    fn assigned_name_for_tag(&self, tag: &Tag) -> Option<String> {
211        self.tags.assigned_name_for_tag(tag)
212    }
213
214    /// Returns a name for a tag, either the assigned name or a generic representation.
215    fn name_for_tag(&self, tag: &Tag) -> String {
216        self.tags.name_for_tag(tag)
217    }
218
219    /// Looks up a tag by its name.
220    fn tag_for_name(&self, name: &str) -> Option<Tag> {
221        self.tags.tag_for_name(name)
222    }
223
224    /// Looks up a tag by its numeric value.
225    fn tag_for_value(&self, value: TagValue) -> Option<Tag> {
226        self.tags.tag_for_value(value)
227    }
228
229    /// Returns a CBOR summarizer for a tag value if one exists.
230    fn summarizer(&self, tag: TagValue) -> Option<&CBORSummarizer> {
231        self.tags.summarizer(tag)
232    }
233
234    /// Returns a name for a tag value, either the assigned name or a generic representation.
235    fn name_for_value(&self, value: TagValue) -> String {
236        self.tags.name_for_value(value)
237    }
238}
239
240/// Default implementation for `FormatContext`, creating an instance with default components.
241impl Default for FormatContext {
242    /// Creates a default `FormatContext` with:
243    /// - Flat formatting disabled (structured formatting)
244    /// - Default tag registry
245    /// - Default known values store (when `known_value` feature is enabled)
246    /// - Default functions store (when `expression` feature is enabled)
247    /// - Default parameters store (when `expression` feature is enabled)
248    fn default() -> Self {
249        Self::new(
250            false,
251            None,
252            #[cfg(feature = "known_value")] None,
253            #[cfg(feature = "expression")] None,
254            #[cfg(feature = "expression")] None
255        )
256    }
257}
258
259/// A container for lazily initializing the global format context.
260///
261/// This type ensures the global format context is only initialized once,
262/// even in multithreaded contexts, and provides thread-safe access to the context.
263pub struct LazyFormatContext {
264    /// Initialization flag to ensure one-time initialization
265    init: Once,
266    /// Thread-safe storage for the format context
267    data: Mutex<Option<FormatContext>>,
268}
269
270impl LazyFormatContext {
271    /// Gets a thread-safe reference to the format context, initializing it if necessary.
272    ///
273    /// On first access, this method initializes the format context with standard
274    /// registrations for tags, known values, functions, and parameters.
275    ///
276    /// # Returns
277    ///
278    /// A mutex guard containing a reference to the global format context.
279    pub fn get(&self) -> std::sync::MutexGuard<'_, Option<FormatContext>> {
280        self.init.call_once(|| {
281            bc_components::register_tags();
282            let tags_binding = dcbor::GLOBAL_TAGS.get();
283            let tags = tags_binding.as_ref().unwrap();
284
285            #[cfg(feature = "known_value")]
286            let known_values_binding = KNOWN_VALUES.get();
287            #[cfg(feature = "known_value")]
288            let known_values = known_values_binding.as_ref().unwrap();
289
290            #[cfg(feature = "expression")]
291            let functions_binding = GLOBAL_FUNCTIONS.get();
292            #[cfg(feature = "expression")]
293            let functions = functions_binding.as_ref().unwrap();
294            #[cfg(feature = "expression")]
295            let parameters_binding = GLOBAL_PARAMETERS.get();
296            #[cfg(feature = "expression")]
297            let parameters = parameters_binding.as_ref().unwrap();
298
299            let context = FormatContext::new(
300                false,
301                Some(tags),
302                #[cfg(feature = "known_value")] Some(known_values),
303                #[cfg(feature = "expression")] Some(functions),
304                #[cfg(feature = "expression")] Some(parameters)
305            );
306            *self.data.lock().unwrap() = Some(context);
307        });
308        self.data.lock().unwrap()
309    }
310}
311
312/// Global singleton instance of `FormatContext` for application-wide use.
313///
314/// Access this using the `with_format_context!` macro.
315pub static GLOBAL_FORMAT_CONTEXT: LazyFormatContext = LazyFormatContext {
316    init: Once::new(),
317    data: Mutex::new(None),
318};
319
320/// A macro to access the global format context for read-only operations.
321///
322/// This macro provides a convenient way to use the global format context without
323/// dealing with mutex locking and unlocking directly.
324///
325/// # Example
326///
327/// ```
328/// # use bc_envelope::prelude::*;
329/// # let e = Envelope::new("Hello.");
330/// let formatted = with_format_context!(|ctx| e.format_opt(Some(ctx)));
331/// ```
332#[macro_export]
333macro_rules! with_format_context {
334    ($action:expr) => {
335        {
336        let binding = $crate::GLOBAL_FORMAT_CONTEXT.get();
337        let context = &*binding.as_ref().unwrap();
338        #[allow(clippy::redundant_closure_call)]
339        $action(context)
340        }
341    };
342}
343
344/// A macro to access the global format context for read-write operations.
345///
346/// This macro provides a convenient way to use and modify the global format context
347/// without dealing with mutex locking and unlocking directly.
348///
349/// # Example
350///
351/// ```
352/// # use bc_envelope::prelude::*;
353/// # use bc_envelope::with_format_context_mut;
354/// with_format_context_mut!(|ctx: &mut FormatContext| {
355///     // Use a mutable reference method instead of set_flat which consumes self
356///     let flat_setting = ctx.is_flat();
357///     // Do something with the flat setting
358///     assert!(!flat_setting);
359/// });
360/// ```
361#[macro_export]
362macro_rules! with_format_context_mut {
363    ($action:expr) => {
364        {
365        let mut binding = $crate::GLOBAL_FORMAT_CONTEXT.get();
366        let context = binding.as_mut().unwrap();
367        #[allow(clippy::redundant_closure_call)]
368        $action(context)
369        }
370    };
371}
372
373/// Registers standard tags and summarizers in a format context.
374///
375/// This function populates a format context with standard tag definitions
376/// and summarizers for various envelope-related types, enabling proper
377/// annotation of formatted output.
378///
379/// # Parameters
380///
381/// * `context` - The format context to register tags in
382pub fn register_tags_in(context: &mut FormatContext) {
383    // Register standard component tags
384    bc_components::register_tags_in(context.tags_mut());
385
386    #[cfg(feature = "known_value")]
387    {
388        // Known value summarizer - formats known values with single quotes
389        let known_values = context.known_values().clone();
390        context.tags_mut().set_summarizer(
391            TAG_KNOWN_VALUE,
392            Arc::new(move |untagged_cbor: CBOR| {
393                Ok(
394                    known_values
395                        .name(KnownValue::from_untagged_cbor(untagged_cbor)?)
396                        .flanked_by("'", "'")
397                )
398            })
399        );
400    }
401
402    // Register expression-related summarizers when the expression feature is enabled
403    #[cfg(feature = "expression")]
404    {
405        use crate::extension::expressions::{ Function, FunctionsStore, Parameter, ParametersStore };
406
407        // Function summarizer - formats functions with special delimiter characters
408        let functions = context.functions().clone();
409        context.tags_mut().set_summarizer(
410            TAG_FUNCTION,
411            Arc::new(move |untagged_cbor: CBOR| {
412                let f = Function::from_untagged_cbor(untagged_cbor)?;
413                Ok(FunctionsStore::name_for_function(&f, Some(&functions)).flanked_by("«", "»"))
414            })
415        );
416
417        // Parameter summarizer - formats parameters with special delimiter characters
418        let parameters = context.parameters().clone();
419        context.tags_mut().set_summarizer(
420            TAG_PARAMETER,
421            Arc::new(move |untagged_cbor: CBOR| {
422                let p = Parameter::from_untagged_cbor(untagged_cbor)?;
423                Ok(ParametersStore::name_for_parameter(&p, Some(&parameters)).flanked_by("❰", "❱"))
424            })
425        );
426
427        // Request summarizer - formats requests with request() notation
428        let cloned_context = context.clone();
429        context.tags_mut().set_summarizer(
430            TAG_REQUEST,
431            Arc::new(move |untagged_cbor: CBOR| {
432                Ok(
433                    Envelope::new(untagged_cbor)
434                        .format_opt(Some(&cloned_context))
435                        .flanked_by("request(", ")")
436                )
437            })
438        );
439
440        // Response summarizer - formats responses with response() notation
441        let cloned_context = context.clone();
442        context.tags_mut().set_summarizer(
443            TAG_RESPONSE,
444            Arc::new(move |untagged_cbor: CBOR| {
445                Ok(
446                    Envelope::new(untagged_cbor)
447                        .format_opt(Some(&cloned_context))
448                        .flanked_by("response(", ")")
449                )
450            })
451        );
452
453        // Event summarizer - formats events with event() notation
454        let cloned_context = context.clone();
455        context.tags_mut().set_summarizer(
456            TAG_EVENT,
457            Arc::new(move |untagged_cbor: CBOR| {
458                Ok(
459                    Envelope::new(untagged_cbor)
460                        .format_opt(Some(&cloned_context))
461                        .flanked_by("event(", ")")
462                )
463            })
464        );
465    }
466}
467
468/// Registers standard tags in the global format context.
469///
470/// This function uses the global format context and registers standard tags
471/// using the `register_tags_in` function. It's a convenience wrapper for
472/// registering tags in the global context.
473pub fn register_tags() {
474    with_format_context_mut!(|context: &mut FormatContext| {
475        register_tags_in(context);
476    });
477}