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
10use qubit_budget::StructureLimits;
11#[cfg(feature = "json")]
12use qubit_budget::json::JsonValueLimits;
13
14use super::RedactionLimitsBuilder;
15
16/// Structural and JSON limits for one redaction operation.
17///
18/// # Examples
19///
20/// ```
21/// use qubit_redact::RedactionLimits;
22///
23/// let mut builder = RedactionLimits::builder();
24/// builder.max_output_bytes(128);
25/// let limits = builder.build();
26/// assert_eq!(limits.max_output_bytes(), 128);
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct RedactionLimits {
30 /// Maximum source bytes admitted for inspection.
31 max_input_bytes: usize,
32 /// Maximum safe bytes retained in output.
33 max_output_bytes: usize,
34 /// Maximum logical scalar bytes passed to a Serde serializer.
35 #[cfg(feature = "serde")]
36 max_serde_payload_bytes: usize,
37 /// Structural limits shared by domain and format traversal.
38 domain: StructureLimits,
39 /// JSON-specific structural and payload limits.
40 #[cfg(feature = "json")]
41 json: JsonValueLimits,
42}
43
44impl RedactionLimits {
45 /// Creates a builder initialized with the standard redaction limits.
46 ///
47 /// # Returns
48 ///
49 /// A mutable builder initialized with finite standard resource ceilings.
50 #[must_use]
51 #[inline(always)]
52 pub fn builder() -> RedactionLimitsBuilder {
53 RedactionLimitsBuilder::default()
54 }
55
56 /// Creates a builder from an immutable limit snapshot.
57 ///
58 /// # Parameters
59 ///
60 /// - `base`: Snapshot whose configured ceilings are copied.
61 ///
62 /// # Returns
63 ///
64 /// A mutable builder preserving every ceiling of the supplied snapshot.
65 #[must_use]
66 #[inline(always)]
67 pub(crate) fn builder_from(base: &Self) -> RedactionLimitsBuilder {
68 RedactionLimitsBuilder::from_limits(base)
69 }
70
71 /// Creates a snapshot from the builder's complete component state.
72 ///
73 /// # Parameters
74 ///
75 /// - `max_input_bytes`: Cumulative source-byte ceiling.
76 /// - `max_output_bytes`: Retained output-byte ceiling.
77 /// - `max_serde_payload_bytes`: Logical scalar-byte ceiling, with Serde
78 /// enabled.
79 /// - `domain`: Structural admission ceilings shared by the transaction.
80 /// - `json`: JSON-specific ceilings, with JSON enabled.
81 ///
82 /// # Returns
83 ///
84 /// A snapshot retaining all supplied ceilings; policy construction
85 /// validates it.
86 #[must_use]
87 #[inline(always)]
88 pub(super) fn from_parts(
89 max_input_bytes: usize,
90 max_output_bytes: usize,
91 #[cfg(feature = "serde")] max_serde_payload_bytes: usize,
92 domain: StructureLimits,
93 #[cfg(feature = "json")] json: JsonValueLimits,
94 ) -> Self {
95 Self {
96 max_input_bytes,
97 max_output_bytes,
98 #[cfg(feature = "serde")]
99 max_serde_payload_bytes,
100 domain,
101 #[cfg(feature = "json")]
102 json,
103 }
104 }
105
106 /// Returns the maximum source bytes one transaction may inspect.
107 ///
108 /// # Returns
109 ///
110 /// Cumulative source bytes admitted by one transaction.
111 #[must_use]
112 #[inline(always)]
113 pub const fn max_input_bytes(&self) -> usize {
114 self.max_input_bytes
115 }
116
117 /// Returns the maximum safe output bytes one transaction may retain.
118 ///
119 /// # Returns
120 ///
121 /// Maximum final output bytes retained by one transaction.
122 #[must_use]
123 #[inline(always)]
124 pub const fn max_output_bytes(&self) -> usize {
125 self.max_output_bytes
126 }
127
128 /// Returns the logical scalar payload limit for a structured Serde scope.
129 ///
130 /// Counts UTF-8 strings/chars, byte slices, and scalar representations.
131 /// Excludes static field names, container framing, and serializer escaping.
132 /// A caller-owned serializer controls its final encoded byte length;
133 /// `Redactor::to_json` additionally enforces `max_output_bytes`
134 /// when the `json` feature is enabled. The default is 16 KiB; zero is
135 /// valid.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use qubit_redact::RedactionLimits;
141 /// let mut builder = RedactionLimits::builder();
142 /// builder.max_serde_payload_bytes(4).max_output_bytes(32);
143 /// assert_eq!(builder.build().max_serde_payload_bytes(), 4);
144 /// ```
145 ///
146 /// # Returns
147 ///
148 /// Logical scalar bytes admitted by one structured Serde scope.
149 #[cfg(feature = "serde")]
150 #[must_use]
151 #[inline(always)]
152 pub const fn max_serde_payload_bytes(&self) -> usize {
153 self.max_serde_payload_bytes
154 }
155
156 /// Returns the maximum nested structural depth.
157 ///
158 /// # Returns
159 ///
160 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
161 #[must_use]
162 #[inline(always)]
163 pub const fn max_depth(&self) -> Option<usize> {
164 self.domain.max_depth()
165 }
166
167 /// Returns the maximum number of structural nodes.
168 ///
169 /// # Returns
170 ///
171 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
172 #[must_use]
173 #[inline(always)]
174 pub const fn max_nodes(&self) -> Option<usize> {
175 self.domain.max_nodes()
176 }
177
178 /// Returns the cumulative item allowance shared by transaction collections.
179 ///
180 /// # Returns
181 ///
182 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
183 #[must_use]
184 #[inline(always)]
185 pub const fn max_collection_items(&self) -> Option<usize> {
186 self.domain.max_sequence_items()
187 }
188
189 /// Returns the maximum raw UTF-8 length of a domain field or classification
190 /// key, checked before normalization and value access. JSON keys also have
191 /// their independent JSON admission limits.
192 ///
193 /// # Returns
194 ///
195 /// `Some(maximum)` bounds raw key bytes; `None` disables this limit.
196 #[must_use]
197 #[inline(always)]
198 pub const fn max_key_bytes(&self) -> Option<usize> {
199 self.domain.max_key_bytes()
200 }
201
202 /// Returns the maximum JSON nesting depth.
203 ///
204 /// # Returns
205 ///
206 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
207 #[cfg(feature = "json")]
208 #[must_use]
209 #[inline(always)]
210 pub const fn max_json_depth(&self) -> Option<usize> {
211 self.json.max_depth()
212 }
213
214 /// Returns the maximum number of JSON nodes.
215 ///
216 /// # Returns
217 ///
218 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
219 #[cfg(feature = "json")]
220 #[must_use]
221 #[inline(always)]
222 pub const fn max_json_nodes(&self) -> Option<usize> {
223 self.json.max_nodes()
224 }
225
226 /// Returns the maximum number of items in one JSON collection.
227 ///
228 /// # Returns
229 ///
230 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
231 #[cfg(feature = "json")]
232 #[must_use]
233 #[inline(always)]
234 pub const fn max_json_collection_items(&self) -> Option<usize> {
235 self.json.max_sequence_items()
236 }
237
238 /// Returns the maximum JSON object-key length.
239 ///
240 /// # Returns
241 ///
242 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
243 #[cfg(feature = "json")]
244 #[must_use]
245 #[inline(always)]
246 pub const fn max_json_key_bytes(&self) -> Option<usize> {
247 self.json.max_key_bytes()
248 }
249
250 /// Returns the maximum JSON string length.
251 ///
252 /// # Returns
253 ///
254 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
255 #[cfg(feature = "json")]
256 #[must_use]
257 #[inline(always)]
258 pub const fn max_json_string_bytes(&self) -> Option<usize> {
259 self.json.max_string_bytes()
260 }
261
262 /// Returns the maximum JSON number representation length.
263 ///
264 /// # Returns
265 ///
266 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
267 #[cfg(feature = "json")]
268 #[must_use]
269 #[inline(always)]
270 pub const fn max_json_number_bytes(&self) -> Option<usize> {
271 self.json.max_number_bytes()
272 }
273
274 /// Returns the cumulative JSON payload-byte maximum.
275 ///
276 /// # Returns
277 ///
278 /// `Some(maximum)` is the configured ceiling; `None` disables this limit.
279 #[cfg(feature = "json")]
280 #[must_use]
281 #[inline(always)]
282 pub const fn max_json_payload_bytes(&self) -> Option<usize> {
283 self.json.max_payload_bytes()
284 }
285
286 /// Returns the internal structural limits for transaction construction.
287 ///
288 /// # Returns
289 ///
290 /// The shared immutable structural admission ceilings.
291 #[must_use]
292 #[inline(always)]
293 pub(crate) const fn structural_limits(&self) -> StructureLimits {
294 self.domain
295 }
296
297 /// Returns the internal JSON limits for transaction construction.
298 ///
299 /// # Returns
300 ///
301 /// The immutable JSON-specific admission ceilings.
302 #[cfg(feature = "json")]
303 #[must_use]
304 #[inline(always)]
305 pub(crate) const fn json_limits(&self) -> JsonValueLimits {
306 self.json
307 }
308
309 /// Validates limits whose values would otherwise reach collection
310 /// allocation code during transaction rendering.
311 ///
312 /// # Errors
313 ///
314 /// Returns [`super::PolicyError::OutputLimitTooLarge`] when the output
315 /// ceiling exceeds the maximum addressable Rust collection capacity.
316 /// With `serde`, an oversized logical payload ceiling returns
317 /// `PolicyError::SerdePayloadLimitTooLarge`.
318 ///
319 /// # Returns
320 ///
321 /// Success when output and enabled Serde payload ceilings fit Rust
322 /// allocation limits.
323 pub(crate) fn validate(&self) -> Result<(), super::PolicyError> {
324 if self.max_output_bytes > isize::MAX as usize {
325 return Err(super::PolicyError::OutputLimitTooLarge {
326 maximum: self.max_output_bytes,
327 });
328 }
329 #[cfg(feature = "serde")]
330 if self.max_serde_payload_bytes > isize::MAX as usize {
331 return Err(super::PolicyError::SerdePayloadLimitTooLarge {
332 maximum: self.max_serde_payload_bytes,
333 });
334 }
335 Ok(())
336 }
337}
338
339impl Default for RedactionLimits {
340 /// Builds the immutable standard limit snapshot.
341 ///
342 /// # Returns
343 ///
344 /// The standard immutable resource limits produced by the default builder.
345 #[inline(always)]
346 fn default() -> Self {
347 Self::builder().build()
348 }
349}