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 },
17 marker::PhantomData,
18};
19
20use crate::{
21 BoundedRedactedDisplay,
22 LogOutputLimit,
23 RedactMapValue,
24 RedactionPolicy,
25 RedactionSession,
26};
27
28use super::bounded_redacted_display::format_bounded;
29use super::internal::mask_byte_limit;
30
31/// A lazy map view that classifies values by their runtime keys.
32///
33/// # Type Parameters
34///
35/// * `'a` - Lifetime of the borrowed map.
36/// * `M` - Borrowed map-like container type.
37/// * `K` - Runtime key type used for field classification.
38/// * `V` - Value type formatted or serialized through redaction.
39#[must_use = "format or serialize the redacted map view"]
40pub struct RedactedMap<'a, M: ?Sized, K: ?Sized = String, V: ?Sized = String> {
41 /// Map borrowed without traversal.
42 map: &'a M,
43 /// Immutable policy snapshot used during formatting.
44 policy: RedactionPolicy,
45 /// Associates the view with the map entry types without storing them.
46 marker: PhantomData<fn() -> (*const K, *const V)>,
47}
48
49impl<'a, M: ?Sized, K: ?Sized, V: ?Sized> RedactedMap<'a, M, K, V> {
50 /// Creates a lazy map view without traversing or cloning the map.
51 ///
52 /// # Parameters
53 ///
54 /// * `map` - String-valued map-like container to borrow.
55 /// * `policy` - Complete policy snapshot owned by the view.
56 ///
57 /// # Returns
58 ///
59 /// A lazy borrowed map view.
60 #[inline(always)]
61 pub const fn new(map: &'a M, policy: RedactionPolicy) -> Self {
62 Self {
63 map,
64 policy,
65 marker: PhantomData,
66 }
67 }
68
69 /// Converts this view into a byte-bounded, log-safe display adapter.
70 ///
71 /// # Parameters
72 ///
73 /// * `limit` - Maximum rendered bytes including any truncation marker.
74 ///
75 /// # Returns
76 ///
77 /// A bounded formatting adapter that owns this redacted map view.
78 #[inline(always)]
79 pub const fn with_output_limit(
80 self,
81 limit: LogOutputLimit,
82 ) -> BoundedRedactedDisplay<Self> {
83 BoundedRedactedDisplay::new(self, limit)
84 }
85
86 /// Converts this view into a byte-bounded display adapter using its policy.
87 ///
88 /// # Returns
89 ///
90 /// A formatting adapter bounded by this view's diagnostic output budget.
91 #[must_use = "format the bounded redacted map display adapter"]
92 #[inline]
93 pub fn with_policy_output_limit(self) -> BoundedRedactedDisplay<Self> {
94 let limit =
95 LogOutputLimit::from(self.policy.limits().diagnostic_event());
96 BoundedRedactedDisplay::new(self, limit)
97 }
98}
99
100impl<M: RedactMapValue<K, V> + ?Sized, K: ?Sized, V: ?Sized> Debug
101 for RedactedMap<'_, M, K, V>
102{
103 /// Formats the map by classifying every value with its corresponding key.
104 ///
105 /// # Parameters
106 ///
107 /// * `formatter` - Destination debug formatter.
108 ///
109 /// # Returns
110 ///
111 /// The formatter result for the complete map.
112 ///
113 /// # Errors
114 ///
115 /// Returns [`fmt::Error`] when the destination rejects an entry or the
116 /// completed map.
117 #[inline(always)]
118 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
119 let session = RedactionSession::diagnostic(&self.policy);
120 let view = RedactedMapSession::new(self.map, &session);
121 if mask_byte_limit().is_some() {
122 return Debug::fmt(&view, formatter);
123 }
124 super::bounded_redacted_display::format_debug_bounded(
125 &view,
126 LogOutputLimit::from(self.policy.limits().diagnostic_event()),
127 formatter,
128 )
129 }
130}
131
132mod session_view {
133 use std::{
134 fmt::{
135 self,
136 Debug,
137 Display,
138 Formatter,
139 Write as _,
140 },
141 marker::PhantomData,
142 };
143
144 use crate::{
145 RedactMapValue,
146 RedactionSession,
147 text::internal::LogEscapeWriter,
148 };
149
150 /// A nested map view that reuses one diagnostic session.
151 #[must_use = "format the nested redacted map view"]
152 pub struct RedactedMapSession<
153 'map,
154 'session,
155 'policy,
156 M: ?Sized,
157 K: ?Sized = String,
158 V: ?Sized = String,
159 > {
160 map: &'map M,
161 session: &'session RedactionSession<'policy>,
162 marker: PhantomData<fn() -> (*const K, *const V)>,
163 }
164
165 impl<'map, 'session, 'policy, M: ?Sized, K: ?Sized, V: ?Sized>
166 RedactedMapSession<'map, 'session, 'policy, M, K, V>
167 {
168 /// Creates a nested map view using an existing diagnostic session.
169 #[inline(always)]
170 pub fn new(
171 map: &'map M,
172 session: &'session RedactionSession<'policy>,
173 ) -> Self {
174 Self {
175 map,
176 session,
177 marker: PhantomData,
178 }
179 }
180 }
181
182 impl<M: RedactMapValue<K, V> + ?Sized, K: ?Sized, V: ?Sized> Debug
183 for RedactedMapSession<'_, '_, '_, M, K, V>
184 {
185 /// Formats map entries through the existing diagnostic session.
186 #[inline(always)]
187 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
188 self.map.fmt_redacted_map(self.session, formatter)
189 }
190 }
191
192 impl<M: RedactMapValue<K, V> + ?Sized, K: ?Sized, V: ?Sized> Display
193 for RedactedMapSession<'_, '_, '_, M, K, V>
194 {
195 /// Escapes the nested map representation for plain-text logs.
196 #[inline(always)]
197 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
198 let mut writer = LogEscapeWriter::new(formatter);
199 write!(&mut writer, "{self:?}")
200 }
201 }
202}
203
204pub use session_view::RedactedMapSession;
205
206impl<M: RedactMapValue<K, V> + ?Sized, K: ?Sized, V: ?Sized> Display
207 for RedactedMap<'_, M, K, V>
208{
209 /// Formats bounded compact redacted debug output and escapes it for
210 /// plain-text logs.
211 ///
212 /// # Parameters
213 ///
214 /// * `formatter` - Destination formatting context.
215 ///
216 /// # Returns
217 ///
218 /// The formatter result for the escaped redacted representation.
219 ///
220 /// # Errors
221 ///
222 /// Returns [`fmt::Error`] when the destination rejects the complete
223 /// log-safe representation.
224 #[inline]
225 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
226 let session = RedactionSession::diagnostic(&self.policy);
227 let view = RedactedMapSession::new(self.map, &session);
228 format_bounded(
229 &view,
230 LogOutputLimit::from(self.policy.limits().diagnostic_event()),
231 formatter,
232 )
233 }
234}
235
236#[cfg(feature = "serde")]
237impl<M: crate::domain::RedactMapSerialize<K, V> + ?Sized, K: ?Sized, V: ?Sized>
238 serde::Serialize for RedactedMap<'_, M, K, V>
239{
240 /// Serializes values after classifying each one by its runtime key.
241 ///
242 /// # Type Parameters
243 ///
244 /// * `S` - Destination Serde serializer type.
245 ///
246 /// # Parameters
247 ///
248 /// * `serializer` - Destination Serde serializer.
249 ///
250 /// # Returns
251 ///
252 /// The serializer's successful map output.
253 ///
254 /// # Errors
255 ///
256 /// Returns the first entry or destination serialization error unchanged.
257 #[inline(always)]
258 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
259 where
260 S: serde::Serializer,
261 {
262 self.map.serialize_redacted_map(&self.policy, serializer)
263 }
264}