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