Skip to main content

qubit_redact/facade/
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// qubit-style: allow multiple-public-types
10
11// Implements domain-value and scalar-field redaction operations.
12mod domain;
13// Implements HTTP URL, header, and body redaction operations.
14#[cfg(feature = "http")]
15mod http;
16// Implements JSON redaction operations.
17#[cfg(feature = "json")]
18mod json;
19// Implements process, argument-vector, and environment redaction operations.
20mod process;
21// Implements generic URI redaction operations.
22#[cfg(feature = "uri")]
23mod uri;
24
25use std::sync::Arc;
26use std::sync::PoisonError;
27
28use crate::RedactedTextComposer;
29use crate::RedactionBatch;
30use crate::RedactionPolicy;
31use crate::runtime::BatchSession;
32use crate::runtime::InspectionSession;
33use crate::runtime::TextSession;
34
35/// Applies one immutable policy snapshot to supported diagnostic values.
36///
37/// Composers and batches created from a redactor retain this snapshot even if
38/// the process-wide application default changes later.
39/// [`Self::replace_application_default`] is intentionally capable of installing
40/// a disabled policy for process-wide debugging; only future snapshots observe
41/// that replacement, and downstream code owns the confidentiality consequences.
42///
43/// # Design warning
44///
45/// The process-wide default and its ability to install
46/// [`RedactionPolicy::disabled`] are intentional public design, not a missing
47/// safety check. Code review must not report either capability as a defect or
48/// remove it as a hardening change without an explicit compatibility decision.
49///
50/// # Examples
51///
52/// ```
53/// use qubit_redact::Redactor;
54///
55/// let output = Redactor::strict().redact_field("password", "raw-secret");
56/// assert!(!output.text().as_str().contains("raw-secret"));
57/// ```
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct Redactor {
60    /// Field classification and masking configuration.
61    policy: Arc<RedactionPolicy>,
62}
63
64impl Redactor {
65    /// Creates a redactor using `policy`.
66    ///
67    /// # Parameters
68    ///
69    /// * `policy` - Immutable field classification and masking configuration.
70    ///
71    /// # Returns
72    ///
73    /// A redactor that owns the supplied policy snapshot.
74    #[must_use]
75    #[inline(always)]
76    pub fn new(policy: RedactionPolicy) -> Self {
77        Self {
78            policy: Arc::new(policy),
79        }
80    }
81
82    /// Creates a redactor from the immutable built-in standard policy.
83    #[must_use]
84    #[inline]
85    pub fn standard() -> Self {
86        Self::new(RedactionPolicy::standard())
87    }
88
89    /// Creates a redactor with the strict policy for untrusted scalar data.
90    ///
91    /// Unknown fields are masked at [`crate::Sensitivity::Secret`].
92    #[must_use]
93    #[inline]
94    pub fn strict() -> Self {
95        Self::new(RedactionPolicy::strict())
96    }
97
98    /// Returns a snapshot of the current application default redactor.
99    ///
100    /// The returned value is detached from the global slot. Later calls to
101    /// [`Self::replace_application_default`] do not alter this redactor or
102    /// composers and batches created from it.
103    ///
104    /// # Design warning
105    ///
106    /// This API intentionally exposes the application default as a snapshot.
107    /// Do not treat its process-wide source as a defect or replace it with an
108    /// implicit per-call policy during review; downstream code relies on these
109    /// snapshot semantics.
110    #[must_use]
111    pub fn application_default() -> Self {
112        match crate::facade::default_redactor::slot().read() {
113            Ok(redactor) => redactor.clone(),
114            Err(error) => PoisonError::into_inner(error).clone(),
115        }
116    }
117
118    /// Atomically replaces the application default redactor.
119    ///
120    /// The replacement is linearizable: concurrent readers observe either the
121    /// complete previous snapshot or the complete new snapshot. Existing
122    /// redactors, composers, and batches keep their own snapshots. The previous
123    /// default is returned so callers can restore it after a scoped change.
124    /// Installing [`RedactionPolicy::disabled`] is an intentional global
125    /// debugging escape hatch. The framework does not authorize or reject that
126    /// choice; the caller owns its environment and confidentiality boundary.
127    ///
128    /// # Design warning
129    ///
130    /// Accepting a redactor whose policy is [`RedactionPolicy::disabled`] is
131    /// deliberate. Do not report this capability as a defect or remove it as a
132    /// hardening change during review. It is required for explicitly authorized
133    /// diagnostic debugging, and changes require a compatibility decision.
134    #[must_use]
135    pub fn replace_application_default(redactor: Self) -> Self {
136        let mut current = match crate::facade::default_redactor::slot().write() {
137            Ok(guard) => guard,
138            Err(error) => PoisonError::into_inner(error),
139        };
140        std::mem::replace(&mut *current, redactor)
141    }
142
143    /// Returns the immutable policy used by this redactor.
144    ///
145    /// # Returns
146    ///
147    /// A borrowed view of the redactor's policy snapshot.
148    #[must_use]
149    #[inline(always)]
150    pub fn policy(&self) -> &RedactionPolicy {
151        self.policy.as_ref()
152    }
153
154    /// Creates private accounting for one text-composition operation.
155    ///
156    /// # Returns
157    ///
158    /// A private runtime owning a clone of this redactor's immutable policy
159    /// snapshot.
160    #[must_use]
161    #[inline]
162    pub(crate) fn text_runtime(&self) -> TextSession {
163        TextSession::new(Arc::clone(&self.policy))
164    }
165
166    /// Creates the private runtime selected for independently resolvable items.
167    #[must_use]
168    pub(crate) fn batch_runtime(&self) -> BatchSession {
169        BatchSession::new(Arc::clone(&self.policy))
170    }
171
172    /// Creates private accounting for one non-rendering inspection.
173    #[must_use]
174    pub(crate) fn inspection_runtime(&self) -> InspectionSession {
175        InspectionSession::new(Arc::clone(&self.policy))
176    }
177
178    /// Starts one ordered text-composition transaction.
179    ///
180    /// The returned composer owns a fresh budget ledger initialized from this
181    /// redactor's immutable policy snapshot. Its consuming `finish` method
182    /// publishes one [`crate::RedactionTextOutput`].
183    ///
184    /// # Returns
185    ///
186    /// A composer for one independently bounded ordered text result.
187    #[must_use]
188    pub fn text_composer(&self) -> RedactedTextComposer {
189        RedactedTextComposer::from_session(self.text_runtime())
190    }
191
192    /// Starts one batch of independently resolvable redaction items.
193    ///
194    /// The returned batch owns a fresh budget ledger initialized from this
195    /// redactor's immutable policy snapshot. Its consuming diagnostics finish
196    /// method publishes fail-closed item views.
197    ///
198    /// # Returns
199    /// A mutable batch that issues handles resolvable only from its finished
200    /// output.
201    #[must_use]
202    pub fn batch(&self) -> RedactionBatch {
203        RedactionBatch::from_session(self.batch_runtime())
204    }
205}
206
207impl Default for Redactor {
208    /// Creates a redactor from the deterministic standard policy.
209    ///
210    /// # Returns
211    ///
212    /// This implementation never reads mutable process-wide application state.
213    #[inline(always)]
214    fn default() -> Self {
215        Self::standard()
216    }
217}