Skip to main content

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}