Skip to main content

qubit_redact/policy/
redaction_limits_builder.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//! Mutable construction of redaction resource ceilings.
9
10use qubit_budget::StructureLimits;
11#[cfg(feature = "json")]
12use qubit_budget::json::JsonValueLimits;
13
14use super::RedactionLimits;
15
16/// Mutable construction state for [`RedactionLimits`].
17///
18/// # Examples
19///
20/// ```
21/// use qubit_redact::RedactionLimits;
22///
23/// let mut builder = RedactionLimits::builder();
24/// builder.max_input_bytes(256).max_output_bytes(64);
25/// let limits = builder.build();
26/// assert_eq!(limits.max_input_bytes(), 256);
27/// assert_eq!(limits.max_output_bytes(), 64);
28/// ```
29#[derive(Debug, Clone, Copy)]
30pub struct RedactionLimitsBuilder {
31    /// Draft maximum source bytes admitted for inspection.
32    max_input_bytes: usize,
33    /// Draft maximum safe bytes retained in output.
34    max_output_bytes: usize,
35    /// Maximum logical scalar bytes passed to a Serde serializer.
36    #[cfg(feature = "serde")]
37    max_serde_payload_bytes: usize,
38    /// Draft structural limits shared by domain and format traversal.
39    domain: StructureLimits,
40    /// Draft JSON-specific structural and payload limits.
41    #[cfg(feature = "json")]
42    json: JsonValueLimits,
43}
44
45impl RedactionLimitsBuilder {
46    /// Copies an immutable snapshot into mutable construction state.
47    ///
48    /// # Parameters
49    ///
50    /// - `base`: Snapshot whose ceilings initialize the builder.
51    ///
52    /// # Returns
53    ///
54    /// A builder whose initial output equals the supplied snapshot.
55    #[must_use]
56    #[inline(always)]
57    pub(super) fn from_limits(base: &RedactionLimits) -> Self {
58        Self {
59            max_input_bytes: base.max_input_bytes(),
60            max_output_bytes: base.max_output_bytes(),
61            #[cfg(feature = "serde")]
62            max_serde_payload_bytes: base.max_serde_payload_bytes(),
63            domain: base.structural_limits(),
64            #[cfg(feature = "json")]
65            json: base.json_limits(),
66        }
67    }
68
69    /// Sets the maximum source bytes one transaction may inspect.
70    ///
71    /// # Parameters
72    ///
73    /// - `maximum`: Cumulative source bytes admitted by one transaction. Zero
74    ///   is permitted.
75    ///
76    /// # Returns
77    ///
78    /// This builder after replacing the selected ceiling.
79    #[inline(always)]
80    pub fn max_input_bytes(&mut self, maximum: usize) -> &mut Self {
81        self.max_input_bytes = maximum;
82        self
83    }
84
85    /// Sets the maximum safe output bytes one transaction may retain.
86    ///
87    /// # Parameters
88    ///
89    /// - `maximum`: Maximum final output bytes retained by one transaction.
90    ///   Zero is permitted.
91    ///
92    /// # Returns
93    ///
94    /// This builder after replacing the selected ceiling.
95    #[inline(always)]
96    pub fn max_output_bytes(&mut self, maximum: usize) -> &mut Self {
97        self.max_output_bytes = maximum;
98        self
99    }
100
101    /// Sets the logical Serde payload allowance independently of encoded
102    /// output.
103    ///
104    /// Zero permits empty scalar payloads. Policy construction rejects values
105    /// above `isize::MAX`; third-party serializer framing is not charged here.
106    ///
107    /// # Parameters
108    ///
109    /// - `maximum`: Logical scalar bytes admitted by one structured Serde
110    ///   scope. Zero is permitted.
111    ///
112    /// # Returns
113    ///
114    /// This builder after replacing the selected ceiling.
115    #[cfg(feature = "serde")]
116    #[inline(always)]
117    pub fn max_serde_payload_bytes(&mut self, maximum: usize) -> &mut Self {
118        self.max_serde_payload_bytes = maximum;
119        self
120    }
121
122    /// Sets the maximum nested domain depth.
123    ///
124    /// # Parameters
125    ///
126    /// - `maximum`: Maximum active structural nesting depth. Zero is permitted.
127    ///
128    /// # Returns
129    ///
130    /// This builder after replacing the selected ceiling.
131    #[inline(always)]
132    pub fn max_depth(&mut self, maximum: usize) -> &mut Self {
133        self.domain = self.domain.into_builder().max_depth(maximum).build();
134        self
135    }
136
137    /// Sets the maximum admitted domain nodes.
138    ///
139    /// # Parameters
140    ///
141    /// - `maximum`: Maximum cumulative structural nodes. Zero is permitted.
142    ///
143    /// # Returns
144    ///
145    /// This builder after replacing the selected ceiling.
146    #[inline(always)]
147    pub fn max_nodes(&mut self, maximum: usize) -> &mut Self {
148        self.domain = self.domain.into_builder().max_nodes(maximum).build();
149        self
150    }
151
152    /// Sets the cumulative item allowance shared by transaction collections.
153    ///
154    /// # Parameters
155    ///
156    /// - `maximum`: Cumulative sequence and map entries across transaction
157    ///   collections. Zero is permitted.
158    ///
159    /// # Returns
160    ///
161    /// This builder after replacing the selected ceiling.
162    #[inline(always)]
163    pub fn max_collection_items(&mut self, maximum: usize) -> &mut Self {
164        self.domain = self
165            .domain
166            .into_builder()
167            .max_sequence_items(maximum)
168            .max_map_entries(maximum)
169            .build();
170        self
171    }
172
173    /// Sets the maximum structural key length.
174    ///
175    /// # Parameters
176    ///
177    /// - `maximum`: Maximum raw structural key bytes before classification.
178    ///   Zero is permitted.
179    ///
180    /// # Returns
181    ///
182    /// This builder after replacing the selected ceiling.
183    #[inline(always)]
184    pub fn max_key_bytes(&mut self, maximum: usize) -> &mut Self {
185        self.domain = self.domain.into_builder().max_key_bytes(maximum).build();
186        self
187    }
188
189    /// Sets the maximum JSON nesting depth.
190    ///
191    /// # Parameters
192    ///
193    /// - `maximum`: Maximum JSON nesting depth. Zero is permitted.
194    ///
195    /// # Returns
196    ///
197    /// This builder after replacing the selected ceiling.
198    #[cfg(feature = "json")]
199    #[inline(always)]
200    pub fn max_json_depth(&mut self, maximum: usize) -> &mut Self {
201        self.json = self.json.into_builder().max_depth(maximum).build();
202        self
203    }
204
205    /// Sets the maximum number of JSON nodes.
206    ///
207    /// # Parameters
208    ///
209    /// - `maximum`: Maximum JSON nodes. Zero is permitted.
210    ///
211    /// # Returns
212    ///
213    /// This builder after replacing the selected ceiling.
214    #[cfg(feature = "json")]
215    #[inline(always)]
216    pub fn max_json_nodes(&mut self, maximum: usize) -> &mut Self {
217        self.json = self.json.into_builder().max_nodes(maximum).build();
218        self
219    }
220
221    /// Sets the maximum number of items in one JSON collection.
222    ///
223    /// # Parameters
224    ///
225    /// - `maximum`: Maximum entries in each JSON array or object. Zero is
226    ///   permitted.
227    ///
228    /// # Returns
229    ///
230    /// This builder after replacing the selected ceiling.
231    #[cfg(feature = "json")]
232    #[inline(always)]
233    pub fn max_json_collection_items(&mut self, maximum: usize) -> &mut Self {
234        self.json = self
235            .json
236            .into_builder()
237            .max_sequence_items(maximum)
238            .max_map_entries(maximum)
239            .build();
240        self
241    }
242
243    /// Sets the maximum JSON object-key length.
244    ///
245    /// # Parameters
246    ///
247    /// - `maximum`: Maximum raw JSON object-key bytes. Zero is permitted.
248    ///
249    /// # Returns
250    ///
251    /// This builder after replacing the selected ceiling.
252    #[cfg(feature = "json")]
253    #[inline(always)]
254    pub fn max_json_key_bytes(&mut self, maximum: usize) -> &mut Self {
255        self.json = self.json.into_builder().max_key_bytes(maximum).build();
256        self
257    }
258
259    /// Sets the maximum JSON string length.
260    ///
261    /// # Parameters
262    ///
263    /// - `maximum`: Maximum JSON string bytes. Zero is permitted.
264    ///
265    /// # Returns
266    ///
267    /// This builder after replacing the selected ceiling.
268    #[cfg(feature = "json")]
269    #[inline(always)]
270    pub fn max_json_string_bytes(&mut self, maximum: usize) -> &mut Self {
271        self.json = self.json.into_builder().max_string_bytes(maximum).build();
272        self
273    }
274
275    /// Sets the maximum JSON number representation length.
276    ///
277    /// # Parameters
278    ///
279    /// - `maximum`: Maximum JSON number representation bytes. Zero is
280    ///   permitted.
281    ///
282    /// # Returns
283    ///
284    /// This builder after replacing the selected ceiling.
285    #[cfg(feature = "json")]
286    #[inline(always)]
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    ///
294    /// # Parameters
295    ///
296    /// - `maximum`: Cumulative logical JSON payload bytes. Zero is permitted.
297    ///
298    /// # Returns
299    ///
300    /// This builder after replacing the selected ceiling.
301    #[cfg(feature = "json")]
302    #[inline(always)]
303    pub fn max_json_payload_bytes(&mut self, maximum: usize) -> &mut Self {
304        self.json = self.json.into_builder().max_payload_bytes(maximum).build();
305        self
306    }
307
308    /// Builds immutable limits.
309    ///
310    /// # Returns
311    ///
312    /// An immutable snapshot of all draft ceilings. Validation of addressable
313    /// output and Serde payload sizes occurs when building the enclosing
314    /// policy.
315    #[must_use]
316    #[inline(always)]
317    pub fn build(self) -> RedactionLimits {
318        RedactionLimits::from_parts(
319            self.max_input_bytes,
320            self.max_output_bytes,
321            #[cfg(feature = "serde")]
322            self.max_serde_payload_bytes,
323            self.domain,
324            #[cfg(feature = "json")]
325            self.json,
326        )
327    }
328}
329
330impl Default for RedactionLimitsBuilder {
331    /// Returns conservative finite defaults for every mutable limit.
332    ///
333    /// # Returns
334    ///
335    /// A builder with finite standard ceilings: 64 KiB input, 16 KiB output,
336    /// 16 KiB Serde payload when enabled, and standard structural/JSON limits.
337    #[inline(always)]
338    fn default() -> Self {
339        Self {
340            max_input_bytes: 64 * 1024,
341            max_output_bytes: 16 * 1024,
342            #[cfg(feature = "serde")]
343            max_serde_payload_bytes: 16 * 1024,
344            domain: StructureLimits::builder()
345                .max_depth(32)
346                .max_nodes(1_024)
347                .max_sequence_items(256)
348                .max_map_entries(256)
349                .max_key_bytes(256)
350                .build(),
351            #[cfg(feature = "json")]
352            json: JsonValueLimits::default(),
353        }
354    }
355}