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}