Skip to main content

qubit_redact/policy/
redaction_limits.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//! Immutable structural limits used by redaction.
9// qubit-style: allow multiple-public-types
10
11use qubit_budget::StructureLimits;
12#[cfg(feature = "json")]
13use qubit_budget::json::JsonValueLimits;
14
15/// Mutable construction state for [`RedactionLimits`].
16#[derive(Debug, Clone, Copy)]
17pub struct RedactionLimitsBuilder {
18    /// Draft maximum source bytes admitted for inspection.
19    max_input_bytes: usize,
20    /// Draft maximum safe bytes retained in output.
21    max_output_bytes: usize,
22    /// Draft structural limits shared by domain and format traversal.
23    domain: StructureLimits,
24    /// Draft JSON-specific structural and payload limits.
25    #[cfg(feature = "json")]
26    json: JsonValueLimits,
27}
28
29/// Structural and JSON limits for one redaction operation.
30///
31/// # Examples
32///
33/// ```
34/// use qubit_redact::RedactionLimits;
35///
36/// let mut builder = RedactionLimits::builder();
37/// builder.max_output_bytes(128);
38/// let limits = builder.build();
39/// assert_eq!(limits.max_output_bytes(), 128);
40/// ```
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct RedactionLimits {
43    /// Maximum source bytes admitted for inspection.
44    max_input_bytes: usize,
45    /// Maximum safe bytes retained in output.
46    max_output_bytes: usize,
47    /// Structural limits shared by domain and format traversal.
48    domain: StructureLimits,
49    /// JSON-specific structural and payload limits.
50    #[cfg(feature = "json")]
51    json: JsonValueLimits,
52}
53
54impl RedactionLimits {
55    /// Creates a builder initialized with the standard redaction limits.
56    #[must_use]
57    pub fn builder() -> RedactionLimitsBuilder {
58        RedactionLimitsBuilder::default()
59    }
60
61    /// Creates a builder from an immutable limit snapshot.
62    #[must_use]
63    pub(crate) fn builder_from(base: &Self) -> RedactionLimitsBuilder {
64        RedactionLimitsBuilder {
65            max_input_bytes: base.max_input_bytes,
66            max_output_bytes: base.max_output_bytes,
67            domain: base.domain,
68            #[cfg(feature = "json")]
69            json: base.json,
70        }
71    }
72
73    /// Returns the internal structural limits for transaction construction.
74    #[must_use]
75    #[inline(always)]
76    pub(crate) const fn structural_limits(&self) -> StructureLimits {
77        self.domain
78    }
79
80    /// Returns the maximum source bytes one transaction may inspect.
81    #[must_use]
82    #[inline(always)]
83    pub const fn max_input_bytes(&self) -> usize {
84        self.max_input_bytes
85    }
86
87    /// Returns the maximum safe output bytes one transaction may retain.
88    #[must_use]
89    #[inline(always)]
90    pub const fn max_output_bytes(&self) -> usize {
91        self.max_output_bytes
92    }
93
94    /// Returns the maximum nested structural depth.
95    #[must_use]
96    #[inline(always)]
97    pub const fn max_depth(&self) -> Option<usize> {
98        self.domain.max_depth()
99    }
100
101    /// Returns the maximum number of structural nodes.
102    #[must_use]
103    #[inline(always)]
104    pub const fn max_nodes(&self) -> Option<usize> {
105        self.domain.max_nodes()
106    }
107
108    /// Returns the shared maximum item count for one sequence or map.
109    #[must_use]
110    #[inline(always)]
111    pub const fn max_collection_items(&self) -> Option<usize> {
112        self.domain.max_sequence_items()
113    }
114
115    /// Returns the maximum structural key length.
116    #[must_use]
117    #[inline(always)]
118    pub const fn max_key_bytes(&self) -> Option<usize> {
119        self.domain.max_key_bytes()
120    }
121
122    /// Validates limits whose values would otherwise reach collection
123    /// allocation code during transaction rendering.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`super::PolicyError::OutputLimitTooLarge`] when the output
128    /// ceiling exceeds the maximum addressable Rust collection capacity.
129    pub(crate) fn validate(&self) -> Result<(), super::PolicyError> {
130        if self.max_output_bytes > isize::MAX as usize {
131            return Err(super::PolicyError::OutputLimitTooLarge {
132                maximum: self.max_output_bytes,
133            });
134        }
135        Ok(())
136    }
137
138    /// Returns the internal JSON limits for transaction construction.
139    #[cfg(feature = "json")]
140    #[must_use]
141    #[inline(always)]
142    pub(crate) const fn json_limits(&self) -> JsonValueLimits {
143        self.json
144    }
145
146    /// Returns the maximum JSON nesting depth.
147    #[cfg(feature = "json")]
148    #[must_use]
149    #[inline(always)]
150    pub const fn max_json_depth(&self) -> Option<usize> {
151        self.json.max_depth()
152    }
153
154    /// Returns the maximum number of JSON nodes.
155    #[cfg(feature = "json")]
156    #[must_use]
157    #[inline(always)]
158    pub const fn max_json_nodes(&self) -> Option<usize> {
159        self.json.max_nodes()
160    }
161
162    /// Returns the maximum number of items in one JSON collection.
163    #[cfg(feature = "json")]
164    #[must_use]
165    #[inline(always)]
166    pub const fn max_json_collection_items(&self) -> Option<usize> {
167        self.json.max_sequence_items()
168    }
169
170    /// Returns the maximum JSON object-key length.
171    #[cfg(feature = "json")]
172    #[must_use]
173    #[inline(always)]
174    pub const fn max_json_key_bytes(&self) -> Option<usize> {
175        self.json.max_key_bytes()
176    }
177
178    /// Returns the maximum JSON string length.
179    #[cfg(feature = "json")]
180    #[must_use]
181    #[inline(always)]
182    pub const fn max_json_string_bytes(&self) -> Option<usize> {
183        self.json.max_string_bytes()
184    }
185
186    /// Returns the maximum JSON number representation length.
187    #[cfg(feature = "json")]
188    #[must_use]
189    #[inline(always)]
190    pub const fn max_json_number_bytes(&self) -> Option<usize> {
191        self.json.max_number_bytes()
192    }
193
194    /// Returns the cumulative JSON payload-byte maximum.
195    #[cfg(feature = "json")]
196    #[must_use]
197    #[inline(always)]
198    pub const fn max_json_payload_bytes(&self) -> Option<usize> {
199        self.json.max_payload_bytes()
200    }
201}
202
203impl RedactionLimitsBuilder {
204    /// Sets the maximum source bytes one transaction may inspect.
205    pub fn max_input_bytes(&mut self, maximum: usize) -> &mut Self {
206        self.max_input_bytes = maximum;
207        self
208    }
209
210    /// Sets the maximum safe output bytes one transaction may retain.
211    pub fn max_output_bytes(&mut self, maximum: usize) -> &mut Self {
212        self.max_output_bytes = maximum;
213        self
214    }
215
216    /// Sets the maximum nested domain depth.
217    pub fn max_depth(&mut self, maximum: usize) -> &mut Self {
218        self.domain = self.domain.into_builder().max_depth(maximum).build();
219        self
220    }
221
222    /// Sets the maximum admitted domain nodes.
223    pub fn max_nodes(&mut self, maximum: usize) -> &mut Self {
224        self.domain = self.domain.into_builder().max_nodes(maximum).build();
225        self
226    }
227
228    /// Sets the maximum items admitted from one collection.
229    pub fn max_collection_items(&mut self, maximum: usize) -> &mut Self {
230        self.domain = self
231            .domain
232            .into_builder()
233            .max_sequence_items(maximum)
234            .max_map_entries(maximum)
235            .build();
236        self
237    }
238
239    /// Sets the maximum structural key length.
240    pub fn max_key_bytes(&mut self, maximum: usize) -> &mut Self {
241        self.domain = self.domain.into_builder().max_key_bytes(maximum).build();
242        self
243    }
244
245    /// Sets the maximum JSON nesting depth.
246    #[cfg(feature = "json")]
247    pub fn max_json_depth(&mut self, maximum: usize) -> &mut Self {
248        self.json = self.json.into_builder().max_depth(maximum).build();
249        self
250    }
251
252    /// Sets the maximum number of JSON nodes.
253    #[cfg(feature = "json")]
254    pub fn max_json_nodes(&mut self, maximum: usize) -> &mut Self {
255        self.json = self.json.into_builder().max_nodes(maximum).build();
256        self
257    }
258
259    /// Sets the maximum number of items in one JSON collection.
260    #[cfg(feature = "json")]
261    pub fn max_json_collection_items(&mut self, maximum: usize) -> &mut Self {
262        self.json = self
263            .json
264            .into_builder()
265            .max_sequence_items(maximum)
266            .max_map_entries(maximum)
267            .build();
268        self
269    }
270
271    /// Sets the maximum JSON object-key length.
272    #[cfg(feature = "json")]
273    pub fn max_json_key_bytes(&mut self, maximum: usize) -> &mut Self {
274        self.json = self.json.into_builder().max_key_bytes(maximum).build();
275        self
276    }
277
278    /// Sets the maximum JSON string length.
279    #[cfg(feature = "json")]
280    pub fn max_json_string_bytes(&mut self, maximum: usize) -> &mut Self {
281        self.json = self.json.into_builder().max_string_bytes(maximum).build();
282        self
283    }
284
285    /// Sets the maximum JSON number representation length.
286    #[cfg(feature = "json")]
287    pub fn max_json_number_bytes(&mut self, maximum: usize) -> &mut Self {
288        self.json = self.json.into_builder().max_number_bytes(maximum).build();
289        self
290    }
291
292    /// Sets the cumulative JSON payload-byte maximum.
293    #[cfg(feature = "json")]
294    pub fn max_json_payload_bytes(&mut self, maximum: usize) -> &mut Self {
295        self.json = self.json.into_builder().max_payload_bytes(maximum).build();
296        self
297    }
298
299    /// Builds immutable limits.
300    #[must_use]
301    pub fn build(self) -> RedactionLimits {
302        RedactionLimits {
303            max_input_bytes: self.max_input_bytes,
304            max_output_bytes: self.max_output_bytes,
305            domain: self.domain,
306            #[cfg(feature = "json")]
307            json: self.json,
308        }
309    }
310}
311
312impl Default for RedactionLimitsBuilder {
313    /// Returns conservative finite defaults for every mutable limit.
314    fn default() -> Self {
315        Self {
316            max_input_bytes: 64 * 1024,
317            max_output_bytes: 16 * 1024,
318            domain: StructureLimits::builder()
319                .max_depth(32)
320                .max_nodes(1_024)
321                .max_sequence_items(256)
322                .max_map_entries(256)
323                .max_key_bytes(256)
324                .build(),
325            #[cfg(feature = "json")]
326            json: JsonValueLimits::default(),
327        }
328    }
329}
330
331impl Default for RedactionLimits {
332    /// Builds the immutable standard limit snapshot.
333    fn default() -> Self {
334        Self::builder().build()
335    }
336}