qubit_redact/redactor.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//! Stateless redaction operations backed by an immutable policy.
9
10use std::borrow::Cow;
11
12use crate::{
13 RedactMapValueMut,
14 RedactedKeyedValue,
15 RedactedText,
16 RedactionPolicy,
17 Sensitivity,
18};
19
20/// Applies one immutable policy to scalar values and string maps.
21#[must_use]
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct Redactor {
24 /// Field classification and masking configuration.
25 policy: RedactionPolicy,
26}
27
28impl Redactor {
29 /// Creates a redactor using `policy`.
30 ///
31 /// # Parameters
32 ///
33 /// * `policy` - Immutable field classification and masking configuration.
34 ///
35 /// # Returns
36 ///
37 /// A redactor that owns the supplied policy snapshot.
38 #[inline(always)]
39 pub const fn new(policy: RedactionPolicy) -> Self {
40 Self { policy }
41 }
42
43 /// Returns the immutable policy used by this redactor.
44 ///
45 /// # Returns
46 ///
47 /// A borrowed view of the redactor's policy snapshot.
48 #[must_use = "use the policy snapshot backing this redactor"]
49 #[inline(always)]
50 pub const fn policy(&self) -> &RedactionPolicy {
51 &self.policy
52 }
53
54 /// Redacts one value according to its field name.
55 ///
56 /// Unknown and explicitly allowed fields retain a borrow of `value`.
57 /// Sensitive fields return the value produced by the configured mask.
58 ///
59 /// # Type Parameters
60 ///
61 /// * `'a` - Lifetime of the input and any borrowed redacted result.
62 ///
63 /// # Parameters
64 ///
65 /// * `field` - Raw field name to classify.
66 /// * `value` - Field value to redact when classified as sensitive.
67 ///
68 /// # Returns
69 ///
70 /// Typed redacted text borrowing safe input where possible.
71 #[must_use = "use the returned redacted value"]
72 #[inline]
73 pub fn redact<'a>(&self, field: &str, value: &'a str) -> RedactedText<'a> {
74 let value = match self.policy.sensitivity_for(field) {
75 Some(level) => self.policy.masking().mask(level, value),
76 None => Cow::Borrowed(value),
77 };
78 RedactedText::new(value)
79 }
80
81 /// Redacts one value at an explicit sensitivity level.
82 ///
83 /// This ignores field classification and allow rules. Use it at a boundary
84 /// where the value is known to be sensitive regardless of its field name.
85 ///
86 /// # Type Parameters
87 ///
88 /// * `'a` - Lifetime of the input and any borrowed redacted result.
89 ///
90 /// # Parameters
91 ///
92 /// * `level` - Sensitivity required by the calling boundary.
93 /// * `value` - Value to mask.
94 ///
95 /// # Returns
96 ///
97 /// Typed redacted text produced by the configured mask for `level`.
98 #[must_use = "use the returned redacted value"]
99 #[inline]
100 pub fn redact_at<'a>(
101 &self,
102 level: Sensitivity,
103 value: &'a str,
104 ) -> RedactedText<'a> {
105 RedactedText::new(self.policy.masking().mask(level, value))
106 }
107
108 /// Creates a lazy redacted view selected by an external key.
109 ///
110 /// The returned view borrows this redactor's policy snapshot. When its key
111 /// is sensitive, it masks the complete value through
112 /// [`RedactValue`](crate::RedactValue). Otherwise it delegates to the
113 /// value's recursive redaction contracts.
114 ///
115 /// # Type Parameters
116 ///
117 /// * `'value` - Lifetime of the borrowed key and value.
118 /// * `T` - Value type rendered or serialized through redaction.
119 ///
120 /// # Parameters
121 ///
122 /// * `key` - Field name used only for policy classification.
123 /// * `value` - Value to render or serialize through the selected policy.
124 ///
125 /// # Returns
126 ///
127 /// A lazy keyed redaction view borrowing `key` and `value`.
128 #[must_use = "format or serialize the returned keyed redaction view"]
129 #[inline(always)]
130 pub fn redact_keyed<'value, T: ?Sized>(
131 &self,
132 key: &'value str,
133 value: &'value T,
134 ) -> RedactedKeyedValue<'value, '_, T> {
135 RedactedKeyedValue::new(key, value, &self.policy)
136 }
137
138 /// Redacts one value while bounding any allocated mask.
139 ///
140 /// # Type Parameters
141 ///
142 /// * `'a` - Lifetime of the input and any borrowed redacted result.
143 ///
144 /// # Parameters
145 ///
146 /// * `field` - Raw field name to classify.
147 /// * `value` - Field value to redact when sensitive.
148 /// * `max_bytes` - Maximum bytes allocated for a generated mask.
149 ///
150 /// # Returns
151 ///
152 /// Typed redacted text whose owned mask does not exceed `max_bytes`.
153 #[cfg(feature = "http")]
154 pub(crate) fn redact_bounded<'a>(
155 &self,
156 field: &str,
157 value: &'a str,
158 max_bytes: usize,
159 ) -> RedactedText<'a> {
160 let value = match self.policy.sensitivity_for(field) {
161 Some(level) => {
162 self.policy.masking().mask_bounded(level, value, max_bytes)
163 }
164 None => Cow::Borrowed(value),
165 };
166 RedactedText::new(value)
167 }
168
169 /// Creates a redacted copy of a text-keyed, mutable text-valued map.
170 ///
171 /// The source map is never modified. Its concrete collection type is
172 /// preserved by cloning the collection before applying in-place redaction.
173 ///
174 /// # Type Parameters
175 ///
176 /// * `M` - Cloneable map-like collection returned after redaction.
177 /// * `K` - Runtime key type used for field classification.
178 /// * `V` - Mutable map-value type redacted in the cloned collection.
179 ///
180 /// # Parameters
181 ///
182 /// * `map` - Map whose values are classified by their corresponding keys.
183 ///
184 /// # Returns
185 ///
186 /// A map of the same type containing redacted values.
187 #[must_use = "use the returned redacted map"]
188 pub fn redact_map<M, K: ?Sized, V: ?Sized>(&self, map: &M) -> M
189 where
190 M: Clone + RedactMapValueMut<K, V>,
191 {
192 let mut redacted = map.clone();
193 RedactMapValueMut::redact_map_in_place(&mut redacted, &self.policy);
194 redacted
195 }
196
197 /// Redacts sensitive values of a text-keyed map in place.
198 ///
199 /// # Type Parameters
200 ///
201 /// * `M` - Mutable map-like collection type.
202 /// * `K` - Runtime key type used for field classification.
203 /// * `V` - Mutable map-value type redacted in place.
204 ///
205 /// # Parameters
206 ///
207 /// * `map` - Mutable map whose values are classified by their keys.
208 #[inline(always)]
209 pub fn redact_map_in_place<M, K: ?Sized, V: ?Sized>(&self, map: &mut M)
210 where
211 M: RedactMapValueMut<K, V> + ?Sized,
212 {
213 RedactMapValueMut::redact_map_in_place(map, &self.policy);
214 }
215}
216
217impl Default for Redactor {
218 /// Creates a redactor from the current global default policy snapshot.
219 ///
220 /// # Returns
221 ///
222 /// A redactor that is unaffected by later policy configuration attempts.
223 #[inline(always)]
224 fn default() -> Self {
225 Self::new(RedactionPolicy::default())
226 }
227}