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