qubit_json/options/json_decode_options.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines the option type used to configure the lenient JSON decoder.
9
10use crate::{
11 ErrorPrivacyPolicy,
12 MarkdownFenceClosing,
13 MarkdownFencePolicy,
14};
15
16/// Configuration switches for [`crate::LenientJsonDecoder`].
17///
18/// Its fields control text normalization, input limits, and error
19/// diagnostics. Defaults are intentionally conservative and cover the most
20/// common non-fully-trusted text inputs without attempting aggressive repair.
21///
22/// # Examples
23///
24/// ```compile_fail
25/// use qubit_json::{JsonDecodeOptions, LenientJsonDecoder};
26///
27/// let options = JsonDecodeOptions::strict();
28/// let _decoder = LenientJsonDecoder::new(options);
29/// let _moved_options = options;
30/// ```
31#[must_use = "JSON decoding options have no effect until used to construct a decoder"]
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct JsonDecodeOptions {
34 /// Controls whether leading and trailing whitespace is removed before any
35 /// other normalization step is applied.
36 trim_whitespace: bool,
37 /// Controls whether a leading UTF-8 byte order mark (`U+FEFF`) is removed
38 /// before parsing.
39 strip_utf8_bom: bool,
40 /// Controls whether and how one outer Markdown code fence is removed.
41 markdown_fence_policy: MarkdownFencePolicy,
42 /// Controls whether raw ASCII control characters inside JSON string
43 /// literals are converted into valid JSON escape sequences.
44 escape_control_chars_in_strings: bool,
45 /// Caps the accepted raw input size in bytes before normalization.
46 ///
47 /// When set to `Some(limit)`, any input whose byte length is greater than
48 /// `limit` is rejected before further processing. When set to `None`, no
49 /// size limit is enforced. This does not bound normalized allocation size:
50 /// escaping a raw control byte can expand it to six ASCII bytes.
51 max_input_bytes: Option<usize>,
52 /// Caps the normalized JSON byte size before the decoder allocates
53 /// repaired text for raw control characters.
54 ///
55 /// When set to `Some(limit)`, the final normalized text must not exceed
56 /// `limit` bytes. When set to `None`, no normalized-size limit is
57 /// enforced.
58 max_normalized_bytes: Option<usize>,
59 /// Controls whether decoding errors retain input-derived serde details.
60 error_privacy_policy: ErrorPrivacyPolicy,
61}
62
63impl JsonDecodeOptions {
64 /// Creates the default lenient option set.
65 ///
66 /// # Returns
67 ///
68 /// An option set that enables every supported normalization rule, applies
69 /// no input-size limit, and redacts input-derived error details.
70 #[inline]
71 pub const fn lenient() -> Self {
72 Self {
73 trim_whitespace: true,
74 strip_utf8_bom: true,
75 markdown_fence_policy: MarkdownFencePolicy::JsonOnly {
76 closing: MarkdownFenceClosing::Optional,
77 },
78 escape_control_chars_in_strings: true,
79 max_input_bytes: None,
80 max_normalized_bytes: None,
81 error_privacy_policy: ErrorPrivacyPolicy::Redacted,
82 }
83 }
84
85 /// Creates an option set that disables all text-rewriting rules.
86 ///
87 /// The decoder still applies empty-input classification, optional raw and
88 /// normalized input-size limits, the configured privacy policy, and stable
89 /// error mapping before or around parsing and deserialization.
90 ///
91 /// # Returns
92 ///
93 /// An option set that leaves input text unchanged, applies no raw or
94 /// normalized size limit,
95 /// delegates parsing and deserialization to `serde_json`, and redacts
96 /// input-derived error details.
97 #[inline]
98 pub const fn strict() -> Self {
99 Self {
100 trim_whitespace: false,
101 strip_utf8_bom: false,
102 markdown_fence_policy: MarkdownFencePolicy::Disabled,
103 escape_control_chars_in_strings: false,
104 max_input_bytes: None,
105 max_normalized_bytes: None,
106 error_privacy_policy: ErrorPrivacyPolicy::Redacted,
107 }
108 }
109
110 /// Returns whether leading and trailing whitespace is removed.
111 ///
112 /// # Returns
113 ///
114 /// `true` when surrounding whitespace is removed; otherwise, `false`.
115 #[inline(always)]
116 #[must_use]
117 pub const fn trim_whitespace(&self) -> bool {
118 self.trim_whitespace
119 }
120
121 /// Returns these options with whitespace trimming enabled or disabled.
122 ///
123 /// # Parameters
124 ///
125 /// * `enabled` - Whether to remove surrounding whitespace.
126 ///
127 /// # Returns
128 ///
129 /// The updated option set.
130 #[inline(always)]
131 pub const fn with_trim_whitespace(mut self, enabled: bool) -> Self {
132 self.trim_whitespace = enabled;
133 self
134 }
135
136 /// Returns whether a leading UTF-8 byte order mark is removed.
137 ///
138 /// # Returns
139 ///
140 /// `true` when a leading UTF-8 byte order mark is removed; otherwise,
141 /// `false`.
142 #[inline(always)]
143 #[must_use]
144 pub const fn strip_utf8_bom(&self) -> bool {
145 self.strip_utf8_bom
146 }
147
148 /// Returns these options with UTF-8 byte order mark stripping configured.
149 ///
150 /// # Parameters
151 ///
152 /// * `enabled` - Whether to remove a leading UTF-8 byte order mark.
153 ///
154 /// # Returns
155 ///
156 /// The updated option set.
157 #[inline(always)]
158 pub const fn with_strip_utf8_bom(mut self, enabled: bool) -> Self {
159 self.strip_utf8_bom = enabled;
160 self
161 }
162
163 /// Returns the policy used to remove one outer Markdown code fence.
164 ///
165 /// # Returns
166 ///
167 /// The configured Markdown fence policy.
168 ///
169 /// # Examples
170 ///
171 /// ```compile_fail
172 /// #![deny(unused_must_use)]
173 /// use qubit_json::JsonDecodeOptions;
174 ///
175 /// let options = JsonDecodeOptions::strict();
176 /// options.markdown_fence_policy();
177 /// ```
178 #[inline(always)]
179 #[must_use = "the configured Markdown fence policy should be inspected"]
180 pub const fn markdown_fence_policy(&self) -> &MarkdownFencePolicy {
181 &self.markdown_fence_policy
182 }
183
184 /// Returns these options with a Markdown fence policy.
185 ///
186 /// # Parameters
187 ///
188 /// * `markdown_fence_policy` - Policy used to recognize and remove one
189 /// outer Markdown code fence.
190 ///
191 /// # Returns
192 ///
193 /// The updated option set.
194 #[inline(always)]
195 pub const fn with_markdown_fence_policy(
196 mut self,
197 markdown_fence_policy: MarkdownFencePolicy,
198 ) -> Self {
199 self.markdown_fence_policy = markdown_fence_policy;
200 self
201 }
202
203 /// Returns whether raw control characters in JSON strings are escaped.
204 ///
205 /// # Returns
206 ///
207 /// `true` when raw ASCII control characters inside JSON strings are
208 /// escaped; otherwise, `false`.
209 #[inline(always)]
210 #[must_use]
211 pub const fn escape_control_chars_in_strings(&self) -> bool {
212 self.escape_control_chars_in_strings
213 }
214
215 /// Returns these options with JSON-string control character escaping
216 /// configured.
217 ///
218 /// # Parameters
219 ///
220 /// * `enabled` - Whether to escape raw ASCII control characters inside JSON
221 /// strings.
222 ///
223 /// # Returns
224 ///
225 /// The updated option set.
226 #[inline(always)]
227 pub const fn with_escape_control_chars_in_strings(
228 mut self,
229 enabled: bool,
230 ) -> Self {
231 self.escape_control_chars_in_strings = enabled;
232 self
233 }
234
235 /// Returns the raw input byte-size limit.
236 ///
237 /// # Returns
238 ///
239 /// `Some(limit)` when accepted raw input is capped at `limit` bytes, or
240 /// `None` when the decoder enforces no input-size limit.
241 #[inline(always)]
242 pub const fn max_input_bytes(&self) -> Option<usize> {
243 self.max_input_bytes
244 }
245
246 /// Returns these options with a raw input byte-size limit.
247 ///
248 /// # Parameters
249 ///
250 /// * `max_input_bytes` - `Some(limit)` to cap the raw input at `limit`
251 /// bytes, or `None` to remove the limit.
252 ///
253 /// # Returns
254 ///
255 /// The updated option set.
256 #[inline(always)]
257 pub const fn with_max_input_bytes(
258 mut self,
259 max_input_bytes: Option<usize>,
260 ) -> Self {
261 self.max_input_bytes = max_input_bytes;
262 self
263 }
264
265 /// Returns the normalized JSON byte-size limit.
266 ///
267 /// # Returns
268 ///
269 /// `Some(limit)` when normalized JSON is capped at `limit` bytes, or
270 /// `None` when the decoder enforces no normalized-size limit.
271 #[inline(always)]
272 pub const fn max_normalized_bytes(&self) -> Option<usize> {
273 self.max_normalized_bytes
274 }
275
276 /// Returns these options with a normalized JSON byte-size limit.
277 ///
278 /// The decoder calculates the normalized size before allocating repaired
279 /// text for raw control characters, so this limit also bounds the
280 /// allocation caused by supported control-character escaping.
281 ///
282 /// # Parameters
283 ///
284 /// * `max_normalized_bytes` - `Some(limit)` to cap normalized JSON at
285 /// `limit` bytes, or `None` to remove the limit.
286 ///
287 /// # Returns
288 ///
289 /// The updated option set.
290 #[inline(always)]
291 pub const fn with_max_normalized_bytes(
292 mut self,
293 max_normalized_bytes: Option<usize>,
294 ) -> Self {
295 self.max_normalized_bytes = max_normalized_bytes;
296 self
297 }
298
299 /// Returns the privacy policy applied to decoding error diagnostics.
300 ///
301 /// # Returns
302 ///
303 /// The configured error privacy policy.
304 #[inline(always)]
305 pub const fn error_privacy_policy(&self) -> ErrorPrivacyPolicy {
306 self.error_privacy_policy
307 }
308
309 /// Returns these options with the requested error privacy policy.
310 ///
311 /// The policy determines whether serde diagnostics derived from input
312 /// values are retained in returned errors.
313 ///
314 /// # Parameters
315 ///
316 /// * `error_privacy_policy` - Policy applied when constructing decoding
317 /// errors.
318 ///
319 /// # Returns
320 ///
321 /// The updated option set.
322 #[inline(always)]
323 pub const fn with_error_privacy_policy(
324 mut self,
325 error_privacy_policy: ErrorPrivacyPolicy,
326 ) -> Self {
327 self.error_privacy_policy = error_privacy_policy;
328 self
329 }
330}
331
332impl Default for JsonDecodeOptions {
333 /// Creates the default lenient option set.
334 ///
335 /// # Returns
336 ///
337 /// The same option set as [`Self::lenient`].
338 #[inline(always)]
339 fn default() -> Self {
340 Self::lenient()
341 }
342}