Skip to main content

qubit_json/
lenient_json_decoder.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 [`LenientJsonDecoder`] type and its public decoding methods.
9
10use serde::de::DeserializeOwned;
11use serde_json::{
12    Value,
13    error::Category,
14    value::RawValue,
15};
16
17use crate::{
18    ErrorPrivacyPolicy,
19    JsonDecodeError,
20    JsonDecodeOptions,
21    JsonTopLevelKind,
22    internal::lenient_json_normalizer::LenientJsonNormalizer,
23};
24
25/// A configurable JSON decoder for non-fully-trusted text inputs.
26///
27/// `LenientJsonDecoder` applies a small set of predictable normalization rules
28/// before delegating actual parsing and deserialization to `serde_json`.
29#[must_use = "a JSON decoder must be used to decode input"]
30#[derive(Debug, Clone, Default)]
31pub struct LenientJsonDecoder {
32    /// Stores the configured normalization pipeline.
33    normalizer: LenientJsonNormalizer,
34}
35
36impl LenientJsonDecoder {
37    /// Creates a decoder with the exact normalization rules in `options`.
38    ///
39    /// # Parameters
40    ///
41    /// * `options` - Immutable normalization and error-diagnostic options.
42    ///
43    /// # Returns
44    ///
45    /// A decoder configured with `options`.
46    #[inline(always)]
47    pub const fn new(options: JsonDecodeOptions) -> Self {
48        Self {
49            normalizer: LenientJsonNormalizer::new(options),
50        }
51    }
52
53    /// Returns the immutable options used by this decoder.
54    ///
55    /// # Returns
56    ///
57    /// The option set supplied when the decoder was created.
58    #[inline(always)]
59    #[must_use = "the decoder options should be inspected or retained"]
60    pub const fn options(&self) -> &JsonDecodeOptions {
61        self.normalizer.options()
62    }
63
64    /// Decodes `input` into the target Rust type `T` without a top-level
65    /// structure constraint.
66    ///
67    /// # Parameters
68    ///
69    /// * `input` - Raw JSON text to normalize and deserialize.
70    ///
71    /// # Returns
72    ///
73    /// The deserialized target value.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`JsonDecodeError`] when input normalization, JSON parsing, or
78    /// target deserialization fails.
79    ///
80    /// # Panics
81    ///
82    /// Panics when the [`serde::Deserialize`] implementation for `T` panics.
83    pub fn decode<T>(&self, input: &str) -> Result<T, JsonDecodeError>
84    where
85        T: DeserializeOwned,
86    {
87        let raw_input_bytes = input.len();
88        let privacy_policy = self.options().error_privacy_policy();
89        let normalized = self.normalizer.normalize(input)?;
90        Self::deserialize_normalized(
91            normalized.as_ref(),
92            raw_input_bytes,
93            normalized.len(),
94            privacy_policy,
95        )
96    }
97
98    /// Decodes UTF-8 input bytes into the target Rust type.
99    ///
100    /// The configured raw byte limit is enforced before UTF-8 validation.
101    /// Valid UTF-8 is borrowed and delegated to the string decoder.
102    ///
103    /// # Parameters
104    ///
105    /// * `input` - Raw JSON bytes to validate, normalize, and deserialize.
106    ///
107    /// # Returns
108    ///
109    /// The deserialized target value.
110    ///
111    /// # Errors
112    ///
113    /// Returns a JSON decode error when the raw byte limit is exceeded, the
114    /// bytes are not valid UTF-8, or subsequent JSON decoding fails.
115    ///
116    /// # Panics
117    ///
118    /// Panics when the [`serde::Deserialize`] implementation for `T` panics.
119    pub fn decode_slice<T>(&self, input: &[u8]) -> Result<T, JsonDecodeError>
120    where
121        T: DeserializeOwned,
122    {
123        let raw_input_bytes = input.len();
124        let privacy_policy = self.options().error_privacy_policy();
125        if let Some(max_input_bytes) = self.options().max_input_bytes()
126            && raw_input_bytes > max_input_bytes
127        {
128            return Err(JsonDecodeError::input_too_large(
129                raw_input_bytes,
130                max_input_bytes,
131                privacy_policy,
132            ));
133        }
134        let input = std::str::from_utf8(input).map_err(|error| {
135            JsonDecodeError::invalid_utf8(
136                error,
137                raw_input_bytes,
138                privacy_policy,
139            )
140        })?;
141        self.decode(input)
142    }
143
144    /// Decodes `input` into `T`, requiring a top-level JSON object.
145    ///
146    /// The target is deserialized directly from normalized text after a
147    /// top-level check, preserving serde's duplicate-field and number handling
148    /// semantics.
149    ///
150    /// # Parameters
151    ///
152    /// * `input` - Raw JSON text to normalize and deserialize.
153    ///
154    /// # Returns
155    ///
156    /// The deserialized target value when the normalized input is a JSON
157    /// object.
158    ///
159    /// # Errors
160    ///
161    /// Returns [`JsonDecodeError`] when normalization or parsing fails, when
162    /// the top-level value is not an object, or when the object cannot be
163    /// deserialized into `T`.
164    ///
165    /// # Panics
166    ///
167    /// Panics when the [`serde::Deserialize`] implementation for `T` panics.
168    #[inline(always)]
169    pub fn decode_object<T>(&self, input: &str) -> Result<T, JsonDecodeError>
170    where
171        T: DeserializeOwned,
172    {
173        self.decode_with_top_level(input, JsonTopLevelKind::Object)
174    }
175
176    /// Decodes `input` into `Vec<T>`, requiring a top-level JSON array.
177    ///
178    /// The elements are deserialized directly from normalized text after a
179    /// top-level check.
180    ///
181    /// # Parameters
182    ///
183    /// * `input` - Raw JSON text to normalize and deserialize.
184    ///
185    /// # Returns
186    ///
187    /// The deserialized elements when the normalized input is a JSON array.
188    ///
189    /// # Errors
190    ///
191    /// Returns [`JsonDecodeError`] when normalization or parsing fails, when
192    /// the top-level value is not an array, or when an element cannot be
193    /// deserialized into `T`.
194    ///
195    /// # Panics
196    ///
197    /// Panics when the [`serde::Deserialize`] implementation for `T` panics.
198    #[inline(always)]
199    pub fn decode_array<T>(
200        &self,
201        input: &str,
202    ) -> Result<Vec<T>, JsonDecodeError>
203    where
204        T: DeserializeOwned,
205    {
206        self.decode_with_top_level(input, JsonTopLevelKind::Array)
207    }
208
209    /// Decodes `input` into a [`serde_json::Value`].
210    ///
211    /// This entry point intentionally constructs a JSON DOM because its public
212    /// return type is [`Value`].
213    ///
214    /// # Parameters
215    ///
216    /// * `input` - Raw JSON text to normalize and parse.
217    ///
218    /// # Returns
219    ///
220    /// The parsed dynamic JSON value.
221    ///
222    /// # Errors
223    ///
224    /// Returns [`JsonDecodeError`] when input normalization or JSON parsing
225    /// fails.
226    pub fn decode_value(&self, input: &str) -> Result<Value, JsonDecodeError> {
227        let raw_input_bytes = input.len();
228        let privacy_policy = self.options().error_privacy_policy();
229        let normalized = self.normalizer.normalize(input)?;
230        Self::parse_value(
231            normalized.as_ref(),
232            raw_input_bytes,
233            normalized.len(),
234            privacy_policy,
235        )
236    }
237
238    /// Decodes input while enforcing an object or array top-level contract.
239    ///
240    /// # Parameters
241    ///
242    /// * `input` - Raw JSON text to normalize and deserialize.
243    /// * `expected` - Required top-level JSON kind.
244    ///
245    /// # Returns
246    ///
247    /// The deserialized target value.
248    ///
249    /// # Errors
250    ///
251    /// Returns [`JsonDecodeError`] when normalization or parsing fails, when
252    /// the validated top-level kind differs from `expected`, or when target
253    /// deserialization fails.
254    ///
255    /// # Panics
256    ///
257    /// Panics from `T`'s `Deserialize` implementation or visitor methods are
258    /// not caught and propagate to the caller.
259    fn decode_with_top_level<T>(
260        &self,
261        input: &str,
262        expected: JsonTopLevelKind,
263    ) -> Result<T, JsonDecodeError>
264    where
265        T: DeserializeOwned,
266    {
267        let raw_input_bytes = input.len();
268        let privacy_policy = self.options().error_privacy_policy();
269        let normalized = self.normalizer.normalize(input)?;
270        let normalized_input_bytes = normalized.len();
271        let actual = JsonTopLevelKind::of_normalized_json(normalized.as_ref());
272        if actual != expected {
273            Self::validate_json(
274                normalized.as_ref(),
275                raw_input_bytes,
276                normalized_input_bytes,
277                privacy_policy,
278            )?;
279            return Err(JsonDecodeError::unexpected_top_level(
280                expected,
281                actual,
282                raw_input_bytes,
283                normalized_input_bytes,
284                privacy_policy,
285            ));
286        }
287        Self::deserialize_normalized(
288            normalized.as_ref(),
289            raw_input_bytes,
290            normalized_input_bytes,
291            privacy_policy,
292        )
293    }
294
295    /// Parses normalized JSON text into a dynamic value.
296    ///
297    /// # Parameters
298    ///
299    /// * `normalized` - Normalized JSON text.
300    /// * `raw_input_bytes` - Input length before normalization.
301    /// * `normalized_input_bytes` - Normalized text length.
302    /// * `privacy_policy` - Policy applied to parse diagnostics.
303    ///
304    /// # Returns
305    ///
306    /// The parsed dynamic JSON value.
307    ///
308    /// # Errors
309    ///
310    /// Returns [`JsonDecodeErrorKind::InvalidJson`](crate::JsonDecodeErrorKind::InvalidJson)
311    /// when `normalized` is not valid JSON.
312    #[inline]
313    fn parse_value(
314        normalized: &str,
315        raw_input_bytes: usize,
316        normalized_input_bytes: usize,
317        privacy_policy: ErrorPrivacyPolicy,
318    ) -> Result<Value, JsonDecodeError> {
319        serde_json::from_str(normalized).map_err(|error| {
320            JsonDecodeError::invalid_json(
321                error,
322                raw_input_bytes,
323                normalized_input_bytes,
324                privacy_policy,
325            )
326        })
327    }
328
329    /// Validates normalized JSON syntax without constructing a value tree.
330    ///
331    /// # Parameters
332    ///
333    /// * `normalized` - Normalized JSON text.
334    /// * `raw_input_bytes` - Input length before normalization.
335    /// * `normalized_input_bytes` - Normalized text length.
336    /// * `privacy_policy` - Policy applied to parse diagnostics.
337    ///
338    /// # Returns
339    ///
340    /// `Ok(())` when the complete normalized text is valid JSON.
341    ///
342    /// # Errors
343    ///
344    /// Returns [`JsonDecodeErrorKind::InvalidJson`](crate::JsonDecodeErrorKind::InvalidJson)
345    /// when validation fails.
346    #[inline]
347    fn validate_json(
348        normalized: &str,
349        raw_input_bytes: usize,
350        normalized_input_bytes: usize,
351        privacy_policy: ErrorPrivacyPolicy,
352    ) -> Result<(), JsonDecodeError> {
353        let _: &RawValue =
354            serde_json::from_str(normalized).map_err(|error| {
355                JsonDecodeError::invalid_json(
356                    error,
357                    raw_input_bytes,
358                    normalized_input_bytes,
359                    privacy_policy,
360                )
361            })?;
362        Ok(())
363    }
364
365    /// Deserializes normalized JSON text into `T`.
366    ///
367    /// # Parameters
368    ///
369    /// * `normalized` - Normalized JSON text.
370    /// * `raw_input_bytes` - Input length before normalization.
371    /// * `normalized_input_bytes` - Normalized text length.
372    /// * `privacy_policy` - Policy applied to decode diagnostics.
373    ///
374    /// # Returns
375    ///
376    /// The deserialized target value.
377    ///
378    /// # Errors
379    ///
380    /// Returns [`JsonDecodeError`] classified as invalid JSON for syntax and
381    /// end-of-input failures. A data error is classified as a deserialization
382    /// failure only when complete syntax validation succeeds.
383    ///
384    /// # Panics
385    ///
386    /// Panics from `T`'s `Deserialize` implementation or visitor methods are
387    /// not caught and propagate to the caller.
388    #[inline]
389    fn deserialize_normalized<T>(
390        normalized: &str,
391        raw_input_bytes: usize,
392        normalized_input_bytes: usize,
393        privacy_policy: ErrorPrivacyPolicy,
394    ) -> Result<T, JsonDecodeError>
395    where
396        T: DeserializeOwned,
397    {
398        serde_json::from_str(normalized).map_err(|error| {
399            Self::map_decode_error(
400                normalized,
401                error,
402                raw_input_bytes,
403                normalized_input_bytes,
404                privacy_policy,
405            )
406        })
407    }
408
409    /// Maps a serde error to the stable public decoder error model.
410    ///
411    /// # Parameters
412    ///
413    /// * `normalized` - Complete normalized JSON text.
414    /// * `error` - Serde JSON error to classify.
415    /// * `raw_input_bytes` - Input length before normalization.
416    /// * `normalized_input_bytes` - Normalized text length.
417    /// * `privacy_policy` - Policy applied to retained diagnostics.
418    ///
419    /// # Returns
420    ///
421    /// A deserialization error for data failures in otherwise valid JSON, or
422    /// an invalid-JSON error when complete syntax validation fails.
423    #[must_use]
424    fn map_decode_error(
425        normalized: &str,
426        error: serde_json::Error,
427        raw_input_bytes: usize,
428        normalized_input_bytes: usize,
429        privacy_policy: ErrorPrivacyPolicy,
430    ) -> JsonDecodeError {
431        match error.classify() {
432            Category::Data => match Self::validate_json(
433                normalized,
434                raw_input_bytes,
435                normalized_input_bytes,
436                privacy_policy,
437            ) {
438                Ok(()) => JsonDecodeError::deserialize(
439                    error,
440                    raw_input_bytes,
441                    normalized_input_bytes,
442                    privacy_policy,
443                ),
444                Err(error) => error,
445            },
446            Category::Io | Category::Syntax | Category::Eof => {
447                JsonDecodeError::invalid_json(
448                    error,
449                    raw_input_bytes,
450                    normalized_input_bytes,
451                    privacy_policy,
452                )
453            }
454        }
455    }
456}