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