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(¶meters)).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}