Skip to main content

qubit_redact/facade/
redaction_batch.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//! Independently resolvable redaction items owned by one batch transaction.
9
10use super::RedactionBatchDiagnostics;
11use super::RedactionBatchHandle;
12use super::RedactionBatchOutput;
13use crate::domain::Redact;
14use crate::runtime::BatchSession;
15use crate::runtime::RedactionHandle;
16
17/// Accumulates independently resolvable redaction items under one budget.
18///
19/// Each operation returns an opaque handle. Handles are usable only with the
20/// [`RedactionBatchDiagnostics`] produced by consuming this batch with
21/// [`Self::finish_for_diagnostics`].
22///
23/// # Examples
24///
25/// ```
26/// use qubit_redact::Redactor;
27///
28/// let mut batch = Redactor::strict().batch();
29/// let handle = batch.redact_field("password", "raw-secret");
30/// let output = batch.finish_for_diagnostics("<redaction incomplete>");
31/// assert!(!output.text(handle).as_str().contains("raw-secret"));
32/// ```
33pub struct RedactionBatch {
34    /// Typed transaction that owns unpublished independently resolvable items.
35    session: BatchSession,
36}
37
38impl RedactionBatch {
39    /// Creates a batch backed by one private runtime transaction.
40    #[must_use]
41    pub(crate) const fn from_session(session: BatchSession) -> Self {
42        Self { session }
43    }
44    /// Redacts one named scalar field and returns its opaque batch handle.
45    ///
46    /// `field` selects the policy rule applied to `value`. The result remains
47    /// unpublished until [`Self::finish_for_diagnostics`] consumes this batch.
48    #[must_use]
49    pub fn redact_field<T>(&mut self, field: &str, value: &T) -> RedactionBatchHandle
50    where
51        T: std::fmt::Display + ?Sized,
52    {
53        let handle = self.session.redact_field(field, value);
54        let (batch_id, item_index) = handle.parts();
55        RedactionBatchHandle { batch_id, item_index }
56    }
57    /// Redacts one domain value and returns its opaque batch handle.
58    ///
59    /// `value` is rendered only through its [`Redact`] implementation; the
60    /// result remains unpublished until
61    /// [`Self::finish_for_diagnostics`] consumes this batch.
62    #[must_use]
63    pub fn redact_value<T>(&mut self, value: &T) -> RedactionBatchHandle
64    where
65        T: Redact + ?Sized,
66    {
67        let handle = self.session.redact_value(value);
68        let (batch_id, item_index) = handle.parts();
69        RedactionBatchHandle { batch_id, item_index }
70    }
71    /// Redacts an explicitly classified argv sequence as one item.
72    ///
73    /// The finite `items` iterator is admitted under this batch's shared
74    /// resource budget before its values are inspected.
75    #[must_use]
76    pub fn redact_argv<'items, I>(&mut self, items: I) -> RedactionBatchHandle
77    where
78        I: IntoIterator<Item = crate::formats::argv::ArgvItem<'items>>,
79    {
80        Self::wrap(self.session.redact_argv(items))
81    }
82    /// Redacts an argv sequence with heuristic option classification as one
83    /// item.
84    ///
85    /// The finite `items` iterator is admitted under this batch's shared
86    /// resource budget before its values are inspected.
87    #[must_use]
88    pub fn redact_heuristic_argv<'items, I>(&mut self, items: I) -> RedactionBatchHandle
89    where
90        I: IntoIterator<Item = crate::formats::argv::ArgvItem<'items>>,
91    {
92        Self::wrap(self.session.redact_heuristic_argv(items))
93    }
94    /// Redacts one environment assignment as one item.
95    ///
96    /// `name` selects the environment rule applied to `value`.
97    #[must_use]
98    pub fn redact_env(&mut self, name: &str, value: &str) -> RedactionBatchHandle {
99        Self::wrap(self.session.redact_env(name, value))
100    }
101    /// Redacts environment assignments as one item.
102    ///
103    /// The finite `pairs` iterator is admitted before the renderer observes
104    /// later entries.
105    #[must_use]
106    pub fn redact_env_pairs<'items, I>(&mut self, pairs: I) -> RedactionBatchHandle
107    where
108        I: IntoIterator<Item = (&'items std::ffi::OsStr, &'items std::ffi::OsStr)>,
109    {
110        Self::wrap(self.session.redact_env_pairs(pairs))
111    }
112    /// Redacts one process command as one item.
113    ///
114    /// `program` precedes `arguments`; `variables` are rendered after argv
115    /// when the shared budget still admits them.
116    #[must_use]
117    pub fn redact_process<'arguments, 'variables, A, E>(
118        &mut self,
119        program: &'arguments std::ffi::OsStr,
120        arguments: A,
121        variables: E,
122    ) -> RedactionBatchHandle
123    where
124        A: IntoIterator<Item = crate::formats::argv::ArgvItem<'arguments>>,
125        E: IntoIterator<Item = (&'variables std::ffi::OsStr, &'variables std::ffi::OsStr)>,
126    {
127        Self::wrap(self.session.redact_process(program, arguments, variables))
128    }
129    /// Redacts one JSON document as one item.
130    ///
131    /// Invalid JSON produces a safe result carrying `InvalidJson` provenance.
132    #[cfg(feature = "json")]
133    #[must_use]
134    pub fn redact_json(&mut self, text: &str) -> RedactionBatchHandle {
135        Self::wrap(self.session.redact_json(text))
136    }
137
138    /// Redacts a borrowed parsed JSON value without taking ownership of it.
139    #[cfg(feature = "json")]
140    #[must_use]
141    pub fn redact_json_value(&mut self, value: &serde_json::Value) -> RedactionBatchHandle {
142        Self::wrap(self.session.redact_json_value(value))
143    }
144    /// Redacts one HTTP URL as one item.
145    ///
146    /// Invalid URLs produce a safe result carrying `InvalidUri` provenance.
147    #[cfg(feature = "http")]
148    #[must_use]
149    pub fn redact_http_url(&mut self, value: &str) -> RedactionBatchHandle {
150        Self::wrap(self.session.redact_http_url(value))
151    }
152    /// Redacts one HTTP header map as one item.
153    #[cfg(feature = "http")]
154    #[must_use]
155    pub fn redact_http_headers(&mut self, headers: &http::HeaderMap) -> RedactionBatchHandle {
156        Self::wrap(self.session.redact_http_headers(headers))
157    }
158    /// Redacts one captured HTTP body as one item using optional parsed
159    /// content-type metadata.
160    #[cfg(feature = "http")]
161    #[must_use]
162    pub fn redact_http_body(
163        &mut self,
164        capture: crate::formats::http::BodyCapture<'_>,
165        content_type: Option<&http::HeaderValue>,
166    ) -> RedactionBatchHandle {
167        Self::wrap(self.session.redact_http_body(capture, content_type))
168    }
169    /// Redacts one captured HTTP body using optional textual content-type
170    /// metadata.
171    #[cfg(feature = "http")]
172    #[must_use]
173    pub fn redact_http_body_with_content_type_text(
174        &mut self,
175        capture: crate::formats::http::BodyCapture<'_>,
176        content_type: Option<&str>,
177    ) -> RedactionBatchHandle {
178        Self::wrap(
179            self.session
180                .redact_http_body_with_content_type_text(capture, content_type),
181        )
182    }
183    /// Redacts one URI as one item.
184    #[cfg(feature = "uri")]
185    #[must_use]
186    pub fn redact_uri(&mut self, value: &str) -> RedactionBatchHandle {
187        Self::wrap(self.session.redact_uri(value))
188    }
189    /// Consumes the batch and publishes its item results and summary.
190    #[must_use]
191    pub(crate) fn finish(self) -> RedactionBatchOutput {
192        RedactionBatchOutput::from_publication(self.session.finish())
193    }
194
195    /// Consumes the batch and prepares fail-closed diagnostic presentation.
196    ///
197    /// Complete items retain their redacted text. Incomplete items and invalid
198    /// handles resolve to the escaped `marker` without returning an error.
199    #[must_use]
200    #[inline]
201    pub fn finish_for_diagnostics(self, marker: &str) -> RedactionBatchDiagnostics {
202        RedactionBatchDiagnostics::new(self.finish(), marker)
203    }
204
205    /// Converts the runtime-private handle into its public batch counterpart.
206    fn wrap(handle: RedactionHandle) -> RedactionBatchHandle {
207        let (batch_id, item_index) = handle.parts();
208        RedactionBatchHandle { batch_id, item_index }
209    }
210}