bc_envelope/format/
format_context.rs

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