Skip to main content

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}