Skip to main content

qubit_redact/domain/
redacted_map.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Lazy borrowed view of a string-valued map-like container.
9
10use std::{
11    fmt::{
12        self,
13        Debug,
14        Display,
15        Formatter,
16        Write as _,
17    },
18    marker::PhantomData,
19};
20
21use crate::{
22    BoundedRedactedDisplay,
23    LogOutputLimit,
24    RedactMapValue,
25    RedactionPolicy,
26    text::internal::LogEscapeWriter,
27};
28
29/// A lazy map view that classifies values by their runtime keys.
30///
31/// # Type Parameters
32///
33/// * `'a` - Lifetime of the borrowed map.
34/// * `M` - Borrowed map-like container type.
35/// * `K` - Runtime key type used for field classification.
36/// * `V` - Value type formatted or serialized through redaction.
37#[must_use = "format or serialize the redacted map view"]
38pub struct RedactedMap<'a, M: ?Sized, K: ?Sized = String, V: ?Sized = String> {
39    /// Map borrowed without traversal.
40    map: &'a M,
41    /// Immutable policy snapshot used during formatting.
42    policy: RedactionPolicy,
43    /// Associates the view with the map entry types without storing them.
44    marker: PhantomData<fn() -> (*const K, *const V)>,
45}
46
47impl<'a, M: ?Sized, K: ?Sized, V: ?Sized> RedactedMap<'a, M, K, V> {
48    /// Creates a lazy map view without traversing or cloning the map.
49    ///
50    /// # Parameters
51    ///
52    /// * `map` - String-valued map-like container to borrow.
53    /// * `policy` - Complete policy snapshot owned by the view.
54    ///
55    /// # Returns
56    ///
57    /// A lazy borrowed map view.
58    #[inline(always)]
59    pub const fn new(map: &'a M, policy: RedactionPolicy) -> Self {
60        Self {
61            map,
62            policy,
63            marker: PhantomData,
64        }
65    }
66
67    /// Converts this view into a byte-bounded, log-safe display adapter.
68    ///
69    /// # Parameters
70    ///
71    /// * `limit` - Maximum rendered bytes including any truncation marker.
72    ///
73    /// # Returns
74    ///
75    /// A display-only adapter that owns this redacted map view.
76    #[inline(always)]
77    pub const fn with_output_limit(
78        self,
79        limit: LogOutputLimit,
80    ) -> BoundedRedactedDisplay<Self> {
81        BoundedRedactedDisplay::new(self, limit)
82    }
83
84    /// Converts this view into a byte-bounded display adapter using its policy.
85    ///
86    /// # Returns
87    ///
88    /// A display-only adapter bounded by this view's diagnostic output budget.
89    #[must_use = "format the bounded redacted map display adapter"]
90    #[inline]
91    pub fn with_policy_output_limit(self) -> BoundedRedactedDisplay<Self> {
92        let limit = LogOutputLimit::from(self.policy.diagnostic_budget());
93        BoundedRedactedDisplay::new(self, limit)
94    }
95}
96
97impl<M: RedactMapValue<K, V> + ?Sized, K: ?Sized, V: ?Sized> Debug
98    for RedactedMap<'_, M, K, V>
99{
100    /// Formats the map by classifying every value with its corresponding key.
101    ///
102    /// # Parameters
103    ///
104    /// * `formatter` - Destination debug formatter.
105    ///
106    /// # Returns
107    ///
108    /// The formatter result for the complete map.
109    ///
110    /// # Errors
111    ///
112    /// Returns [`fmt::Error`] when the destination rejects an entry or the
113    /// completed map.
114    #[inline(always)]
115    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
116        self.map.fmt_redacted_map(&self.policy, formatter)
117    }
118}
119
120impl<M: RedactMapValue<K, V> + ?Sized, K: ?Sized, V: ?Sized> Display
121    for RedactedMap<'_, M, K, V>
122{
123    /// Formats compact redacted debug output and escapes it for plain-text
124    /// logs.
125    ///
126    /// # Parameters
127    ///
128    /// * `formatter` - Destination formatting context.
129    ///
130    /// # Returns
131    ///
132    /// The formatter result for the escaped redacted representation.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`fmt::Error`] when the destination rejects the complete
137    /// log-safe representation.
138    #[inline]
139    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
140        let mut writer = LogEscapeWriter::new(formatter);
141        write!(&mut writer, "{self:?}")
142    }
143}
144
145#[cfg(feature = "serde")]
146impl<M: crate::domain::RedactMapSerialize<K, V> + ?Sized, K: ?Sized, V: ?Sized>
147    serde::Serialize for RedactedMap<'_, M, K, V>
148{
149    /// Serializes values after classifying each one by its runtime key.
150    ///
151    /// # Type Parameters
152    ///
153    /// * `S` - Destination Serde serializer type.
154    ///
155    /// # Parameters
156    ///
157    /// * `serializer` - Destination Serde serializer.
158    ///
159    /// # Returns
160    ///
161    /// The serializer's successful map output.
162    ///
163    /// # Errors
164    ///
165    /// Returns the first entry or destination serialization error unchanged.
166    #[inline(always)]
167    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168    where
169        S: serde::Serializer,
170    {
171        self.map.serialize_redacted_map(&self.policy, serializer)
172    }
173}