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