qubit_redact/domain/redacted_keyed_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 map whose values support recursive redaction.
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 Redact,
25 RedactValue,
26 RedactedKeyedValue,
27 RedactionPolicy,
28 text::internal::LogEscapeWriter,
29};
30
31/// A lazy map view that classifies each value by its key before recursion.
32///
33/// Unknown and explicitly allowed keys delegate to the corresponding value's
34/// [`Redact`] implementation. Sensitive keys mask the complete value through
35/// [`RedactValue`].
36///
37/// # Type Parameters
38///
39/// * `'a` - Lifetime of the borrowed map.
40/// * `M` - Borrowed map-like container type.
41/// * `K` - Runtime key type used for field classification.
42/// * `V` - Value type recursively rendered through redaction.
43#[must_use = "format the recursive keyed redaction view"]
44pub struct RedactedKeyedMap<
45 'a,
46 M: ?Sized,
47 K: ?Sized = String,
48 V: ?Sized = String,
49> {
50 /// Map borrowed without traversal.
51 map: &'a M,
52 /// Immutable policy snapshot shared by every keyed value view.
53 policy: RedactionPolicy,
54 /// Associates the view with the map entry types without storing them.
55 marker: PhantomData<fn() -> (*const K, *const V)>,
56}
57
58impl<'a, M: ?Sized, K: ?Sized, V: ?Sized> RedactedKeyedMap<'a, M, K, V> {
59 /// Creates a lazy recursive keyed map view without traversing the map.
60 ///
61 /// # Parameters
62 ///
63 /// * `map` - Map-like container to borrow.
64 /// * `policy` - Complete policy snapshot owned by the map view.
65 ///
66 /// # Returns
67 ///
68 /// A lazy borrowed map view that shares its policy across all entries.
69 #[must_use = "format the recursive keyed redaction view"]
70 #[inline(always)]
71 pub const fn new(map: &'a M, policy: RedactionPolicy) -> Self {
72 Self {
73 map,
74 policy,
75 marker: PhantomData,
76 }
77 }
78
79 /// Converts this view into a byte-bounded, log-safe display adapter.
80 ///
81 /// # Parameters
82 ///
83 /// * `limit` - Maximum rendered bytes including any truncation marker.
84 ///
85 /// # Returns
86 ///
87 /// A display-only adapter that owns this recursive keyed map view.
88 #[must_use = "format the bounded recursive keyed map display adapter"]
89 #[inline(always)]
90 pub const fn with_output_limit(
91 self,
92 limit: LogOutputLimit,
93 ) -> BoundedRedactedDisplay<Self> {
94 BoundedRedactedDisplay::new(self, limit)
95 }
96
97 /// Converts this view into a byte-bounded display adapter using its policy.
98 ///
99 /// # Returns
100 ///
101 /// A display-only adapter bounded by this view's diagnostic output budget.
102 #[must_use = "format the bounded recursive keyed map display adapter"]
103 #[inline]
104 pub fn with_policy_output_limit(self) -> BoundedRedactedDisplay<Self> {
105 let limit = LogOutputLimit::from(self.policy.diagnostic_budget());
106 BoundedRedactedDisplay::new(self, limit)
107 }
108}
109
110impl<
111 M: ?Sized,
112 K: AsRef<str> + Debug + ?Sized,
113 V: Redact + RedactValue + ?Sized,
114> Debug for RedactedKeyedMap<'_, M, K, V>
115where
116 for<'entry> &'entry M: IntoIterator<Item = (&'entry K, &'entry V)>,
117{
118 /// Formats each entry through its key-selected redaction behavior.
119 ///
120 /// # Parameters
121 ///
122 /// * `formatter` - Destination debug formatter.
123 ///
124 /// # Returns
125 ///
126 /// The formatter result for the complete map.
127 ///
128 /// # Errors
129 ///
130 /// Returns [`fmt::Error`] when the destination rejects an entry or the
131 /// completed map.
132 #[inline]
133 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
134 let mut output = formatter.debug_map();
135 for (key, value) in self.map {
136 output.entry(
137 &key,
138 &RedactedKeyedValue::new(key.as_ref(), value, &self.policy),
139 );
140 }
141 output.finish()
142 }
143}
144
145impl<
146 M: ?Sized,
147 K: AsRef<str> + Debug + ?Sized,
148 V: Redact + RedactValue + ?Sized,
149> Display for RedactedKeyedMap<'_, M, K, V>
150where
151 for<'entry> &'entry M: IntoIterator<Item = (&'entry K, &'entry V)>,
152{
153 /// Formats compact redacted debug output and escapes it for plain-text
154 /// logs.
155 ///
156 /// # Parameters
157 ///
158 /// * `formatter` - Destination formatting context.
159 ///
160 /// # Returns
161 ///
162 /// The formatter result for the escaped redacted representation.
163 ///
164 /// # Errors
165 ///
166 /// Returns [`fmt::Error`] when the destination rejects the complete
167 /// log-safe representation.
168 #[inline]
169 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
170 let mut writer = LogEscapeWriter::new(formatter);
171 write!(&mut writer, "{self:?}")
172 }
173}