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}