Skip to main content

qubit_redact/facade/
redaction_usage.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Resource accounting for one redaction transaction.
9
10/// Measured resource use for one redaction transaction.
11///
12/// # Examples
13///
14/// ```
15/// use qubit_redact::Redactor;
16///
17/// let output = Redactor::standard().redact_field("id", "42");
18/// assert_eq!(output.summary().usage().inspected_input_bytes(), 4);
19/// assert_eq!(output.summary().usage().output_bytes(), 2);
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct RedactionUsage {
23    /// Bytes presented at public input boundaries.
24    presented_input_bytes: usize,
25    /// Presented bytes admitted for inspection.
26    inspected_input_bytes: usize,
27    /// Escaped bytes retained in final output.
28    output_bytes: usize,
29    /// Structural nodes admitted during traversal.
30    visited_nodes: usize,
31    /// Sequence and map items admitted during traversal.
32    visited_collection_items: usize,
33    /// Greatest active structural depth observed.
34    max_depth: usize,
35    /// Known bytes omitted at admission boundaries.
36    omitted_input_bytes: Option<usize>,
37}
38
39impl Default for RedactionUsage {
40    /// Creates the empty resource measurement used by a fresh transaction.
41    ///
42    /// # Returns
43    ///
44    /// An empty measurement with all counters zero and known zero omissions.
45    #[inline(always)]
46    fn default() -> Self {
47        Self::empty()
48    }
49}
50
51impl RedactionUsage {
52    /// Creates an empty measurement for a newly started transaction.
53    ///
54    /// # Returns
55    ///
56    /// An empty measurement with all counters zero and known zero omissions.
57    #[must_use]
58    #[inline(always)]
59    pub const fn empty() -> Self {
60        Self {
61            presented_input_bytes: 0,
62            inspected_input_bytes: 0,
63            output_bytes: 0,
64            visited_nodes: 0,
65            visited_collection_items: 0,
66            max_depth: 0,
67            omitted_input_bytes: Some(0),
68        }
69    }
70
71    /// Returns the bytes supplied by callers before input admission.
72    ///
73    /// # Returns
74    ///
75    /// Bytes presented at admitted or rejected public input boundaries.
76    #[must_use]
77    #[inline(always)]
78    pub const fn presented_input_bytes(self) -> usize {
79        self.presented_input_bytes
80    }
81
82    /// Returns the bytes the transaction actually inspected.
83    ///
84    /// # Returns
85    ///
86    /// Source bytes successfully admitted for inspection, even if later
87    /// discarded.
88    #[must_use]
89    #[inline(always)]
90    pub const fn inspected_input_bytes(self) -> usize {
91        self.inspected_input_bytes
92    }
93
94    /// Returns the final escaped bytes retained by the transaction.
95    ///
96    /// # Returns
97    ///
98    /// Escaped bytes retained in the completed text or batch output.
99    #[must_use]
100    #[inline(always)]
101    pub const fn output_bytes(self) -> usize {
102        self.output_bytes
103    }
104
105    /// Returns the admitted domain or format nodes visited by the transaction.
106    ///
107    /// # Returns
108    ///
109    /// Structural nodes successfully admitted during traversal.
110    #[must_use]
111    #[inline(always)]
112    pub const fn visited_nodes(self) -> usize {
113        self.visited_nodes
114    }
115
116    /// Returns the admitted collection items visited by the transaction.
117    ///
118    /// # Returns
119    ///
120    /// Sequence and map entries successfully admitted during traversal.
121    #[must_use]
122    #[inline(always)]
123    pub const fn visited_collection_items(self) -> usize {
124        self.visited_collection_items
125    }
126
127    /// Returns the greatest active structural depth observed by the
128    /// transaction.
129    ///
130    /// # Returns
131    ///
132    /// The greatest active structural depth, or zero when no node was admitted.
133    #[must_use]
134    #[inline(always)]
135    pub const fn max_depth(self) -> usize {
136        self.max_depth
137    }
138
139    /// Returns omitted source bytes when the source length is known.
140    ///
141    /// # Returns
142    ///
143    /// `Some(bytes)` counts known omissions; `None` means at least one source
144    /// had unknown total length, so the aggregate omission is unknown.
145    #[must_use]
146    #[inline(always)]
147    pub const fn omitted_input_bytes(self) -> Option<usize> {
148        self.omitted_input_bytes
149    }
150
151    /// Adds bytes written to the final output buffer.
152    ///
153    /// # Parameters
154    ///
155    /// - `bytes`: Additional retained output bytes.
156    ///
157    /// # Returns
158    ///
159    /// A copy with output bytes added using saturating arithmetic.
160    #[must_use]
161    #[inline(always)]
162    pub(crate) const fn with_added_output_bytes(mut self, bytes: usize) -> Self {
163        self.output_bytes = self.output_bytes.saturating_add(bytes);
164        self
165    }
166
167    /// Records input supplied to and, when admitted, inspected by an adapter.
168    ///
169    /// # Parameters
170    ///
171    /// - `presented`: Additional source bytes presented at the boundary.
172    /// - `inspected`: Additional source bytes admitted for inspection.
173    ///
174    /// # Returns
175    ///
176    /// A copy with cumulative byte counts and known omissions updated;
177    /// previously unknown omissions remain unknown.
178    #[must_use]
179    #[inline]
180    pub(crate) const fn with_input(mut self, presented: usize, inspected: usize) -> Self {
181        self.presented_input_bytes = self.presented_input_bytes.saturating_add(presented);
182        self.inspected_input_bytes = self.inspected_input_bytes.saturating_add(inspected);
183        self.omitted_input_bytes = match self.omitted_input_bytes {
184            Some(omitted) => Some(omitted.saturating_add(presented.saturating_sub(inspected))),
185            None => None,
186        };
187        self
188    }
189
190    /// Records input whose omitted-byte count is supplied by the source.
191    ///
192    /// # Parameters
193    ///
194    /// - `presented`: Additional source bytes presented at the boundary.
195    /// - `inspected`: Additional source bytes admitted for inspection.
196    /// - `omitted`: Some known omitted bytes, or None when the total source
197    ///   length is unknown.
198    ///
199    /// # Returns
200    ///
201    /// A copy with cumulative input counts and source-reported omissions;
202    /// any unknown omission makes the aggregate unknown.
203    #[cfg(feature = "http")]
204    #[must_use]
205    #[inline]
206    pub(crate) const fn with_source_input(
207        mut self,
208        presented: usize,
209        inspected: usize,
210        omitted: Option<usize>,
211    ) -> Self {
212        self.presented_input_bytes = self.presented_input_bytes.saturating_add(presented);
213        self.inspected_input_bytes = self.inspected_input_bytes.saturating_add(inspected);
214        self.omitted_input_bytes = match (self.omitted_input_bytes, omitted) {
215            (Some(previous), Some(current)) => Some(previous.saturating_add(current)),
216            _ => None,
217        };
218        self
219    }
220
221    /// Records one admitted structural node.
222    ///
223    /// # Parameters
224    ///
225    /// - `depth`: Active structural depth of the newly admitted node.
226    ///
227    /// # Returns
228    ///
229    /// A copy counting one additional node and retaining the greatest depth.
230    #[must_use]
231    #[inline]
232    pub(crate) const fn with_domain_node(mut self, depth: usize) -> Self {
233        self.visited_nodes = self.visited_nodes.saturating_add(1);
234        self.max_depth = if self.max_depth > depth { self.max_depth } else { depth };
235        self
236    }
237
238    /// Records one admitted collection item.
239    ///
240    /// # Returns
241    ///
242    /// A copy counting one additional collection item using saturating
243    /// arithmetic.
244    #[must_use]
245    #[inline(always)]
246    pub(crate) const fn with_collection_item(mut self) -> Self {
247        self.visited_collection_items = self.visited_collection_items.saturating_add(1);
248        self
249    }
250}