Skip to main content

qubit_json/
lenient_json_decoder.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Defines the [`LenientJsonDecoder`] type and its public decoding methods.
11//!
12
13use serde::de::DeserializeOwned;
14use serde_json::{
15    Value,
16    error::Category,
17};
18
19use crate::{
20    JsonDecodeError,
21    JsonDecodeOptions,
22    JsonTopLevelKind,
23    lenient_json_normalizer::LenientJsonNormalizer,
24};
25
26/// A configurable JSON decoder for non-fully-trusted text inputs.
27///
28/// `LenientJsonDecoder` applies a small set of predictable normalization rules
29/// before delegating actual parsing and deserialization to `serde_json`.
30///
31/// The decoder itself is stateless aside from its immutable configuration, so a
32/// single instance can be reused across many decoding calls.
33#[derive(Debug, Clone, Default)]
34pub struct LenientJsonDecoder {
35    /// Stores the immutable normalization and decoding configuration used by
36    /// this decoder instance.
37    normalizer: LenientJsonNormalizer,
38}
39
40impl LenientJsonDecoder {
41    /// Creates a decoder with the exact normalization rules in `options`.
42    ///
43    /// Reusing a decoder instance is recommended when multiple inputs should
44    /// follow the same lenient decoding policy.
45    #[must_use]
46    pub const fn new(options: JsonDecodeOptions) -> Self {
47        Self {
48            normalizer: LenientJsonNormalizer::new(options),
49        }
50    }
51
52    /// Returns the immutable options used by this decoder.
53    ///
54    /// This accessor allows callers to inspect the effective configuration
55    /// without cloning the decoder or duplicating the options elsewhere.
56    #[must_use]
57    pub const fn options(&self) -> &JsonDecodeOptions {
58        self.normalizer.options()
59    }
60
61    /// Decodes `input` into the target Rust type `T`.
62    ///
63    /// This method does not constrain the JSON top-level structure. Arrays,
64    /// objects, scalars, and any other JSON value kinds are all allowed as long
65    /// as they can be deserialized into `T`.
66    ///
67    /// The generic type `T` must implement [`DeserializeOwned`] because this
68    /// method deserializes directly from normalized text and does not return
69    /// values borrowing from the input.
70    ///
71    /// # Errors
72    ///
73    /// Returns [`JsonDecodeError`] when the input becomes empty after
74    /// normalization, when the normalized text is not valid JSON, or when the
75    /// parsed JSON value cannot be deserialized into `T`.
76    ///
77    /// # Examples
78    ///
79    /// ```rust
80    /// use qubit_json::LenientJsonDecoder;
81    ///
82    /// let decoder = LenientJsonDecoder::default();
83    /// let value: u64 = decoder
84    ///     .decode("42")
85    ///     .expect("a numeric JSON scalar should decode into u64");
86    ///
87    /// assert_eq!(value, 42);
88    /// ```
89    pub fn decode<T>(&self, input: &str) -> Result<T, JsonDecodeError>
90    where
91        T: DeserializeOwned,
92    {
93        let normalized = self.normalizer.normalize(input)?;
94        Self::deserialize_normalized(normalized.as_ref(), normalized.len())
95    }
96
97    /// Decodes `input` into a target type `T`, requiring a top-level JSON
98    /// object.
99    ///
100    /// This method is useful for APIs that require a structured object at the
101    /// top level and want an explicit error when an array or scalar is
102    /// received.
103    ///
104    /// # Errors
105    ///
106    /// Returns [`JsonDecodeError`] when the input cannot be normalized into a
107    /// valid JSON value, when the top-level JSON kind is not an object, or
108    /// when the object cannot be deserialized into `T`.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use qubit_json::LenientJsonDecoder;
114    ///
115    /// let decoder = LenientJsonDecoder::default();
116    /// let value: serde_json::Value = decoder
117    ///     .decode_object("```json\n{\"ok\":true}\n```")
118    ///     .expect("a fenced JSON object should decode into a value");
119    ///
120    /// assert_eq!(value["ok"], true);
121    /// ```
122    pub fn decode_object<T>(&self, input: &str) -> Result<T, JsonDecodeError>
123    where
124        T: DeserializeOwned,
125    {
126        self.decode_with_top_level(input, JsonTopLevelKind::Object)
127    }
128
129    /// Decodes `input` into a `Vec<T>`, requiring a top-level JSON array.
130    ///
131    /// This method should be preferred over [`Self::decode`] when the caller
132    /// wants an explicit top-level array contract instead of relying on the
133    /// target type alone.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`JsonDecodeError`] when the input cannot be normalized into a
138    /// valid JSON value, when the top-level JSON kind is not an array, or when
139    /// the array cannot be deserialized into `Vec<T>`.
140    ///
141    /// # Examples
142    ///
143    /// ```rust
144    /// use qubit_json::{JsonDecodeErrorKind, LenientJsonDecoder};
145    ///
146    /// let decoder = LenientJsonDecoder::default();
147    /// let error = decoder
148    ///     .decode_array::<serde_json::Value>("{\"ok\":true}")
149    ///     .expect_err("a top-level object should fail an array contract");
150    ///
151    /// assert_eq!(error.kind, JsonDecodeErrorKind::UnexpectedTopLevel);
152    /// ```
153    pub fn decode_array<T>(&self, input: &str) -> Result<Vec<T>, JsonDecodeError>
154    where
155        T: DeserializeOwned,
156    {
157        self.decode_with_top_level(input, JsonTopLevelKind::Array)
158    }
159
160    /// Decodes `input` into a [`serde_json::Value`].
161    ///
162    /// This is the lowest-level public entry point. It exposes the normalized
163    /// and parsed JSON value before any additional type-specific
164    /// deserialization is attempted.
165    ///
166    /// # Errors
167    ///
168    /// Returns [`JsonDecodeError`] when the input is empty after normalization
169    /// or when the normalized text is not valid JSON syntax.
170    ///
171    /// # Examples
172    ///
173    /// ```rust
174    /// use qubit_json::{JsonDecodeOptions, LenientJsonDecoder};
175    ///
176    /// let decoder = LenientJsonDecoder::new(JsonDecodeOptions {
177    ///     max_input_bytes: Some(16),
178    ///     ..JsonDecodeOptions::default()
179    /// });
180    /// let value = decoder
181    ///     .decode_value("{\"ok\":true}")
182    ///     .expect("input within the size limit should decode");
183    ///
184    /// assert_eq!(value["ok"], true);
185    /// ```
186    pub fn decode_value(&self, input: &str) -> Result<Value, JsonDecodeError> {
187        let (value, _) = self.parse_input_as_value(input)?;
188        Ok(value)
189    }
190
191    /// Normalizes input text and parses it as a JSON value.
192    fn parse_input_as_value(&self, input: &str) -> Result<(Value, usize), JsonDecodeError> {
193        let normalized = self.normalizer.normalize(input)?;
194        let input_bytes = normalized.len();
195        let value = Self::parse_value(normalized.as_ref())?;
196        Ok((value, input_bytes))
197    }
198
199    /// Decodes input after enforcing a required top-level JSON kind.
200    fn decode_with_top_level<T>(
201        &self,
202        input: &str,
203        expected: JsonTopLevelKind,
204    ) -> Result<T, JsonDecodeError>
205    where
206        T: DeserializeOwned,
207    {
208        let (value, input_bytes) = self.parse_input_as_value(input)?;
209        Self::ensure_top_level_from_value(&value, expected)?;
210        Self::deserialize_value(value, input_bytes)
211    }
212
213    /// Parses normalized text into a JSON value.
214    ///
215    /// Syntax failures are mapped to the crate error model with normalized
216    /// input byte length included for diagnostics.
217    fn parse_value(normalized: &str) -> Result<Value, JsonDecodeError> {
218        serde_json::from_str(normalized)
219            .map_err(|error| JsonDecodeError::invalid_json(error, Some(normalized.len())))
220    }
221
222    /// Verifies that a parsed JSON value has the required top-level kind.
223    fn ensure_top_level_from_value(
224        value: &Value,
225        expected: JsonTopLevelKind,
226    ) -> Result<(), JsonDecodeError> {
227        let actual = JsonTopLevelKind::of(value);
228        if actual != expected {
229            return Err(JsonDecodeError::unexpected_top_level(expected, actual));
230        }
231        Ok(())
232    }
233
234    /// Deserializes normalized JSON text into the target type.
235    fn deserialize_normalized<T>(normalized: &str, input_bytes: usize) -> Result<T, JsonDecodeError>
236    where
237        T: DeserializeOwned,
238    {
239        serde_json::from_str(normalized).map_err(|error| Self::map_decode_error(error, input_bytes))
240    }
241
242    /// Deserializes a parsed JSON value into the target type.
243    fn deserialize_value<T>(value: Value, input_bytes: usize) -> Result<T, JsonDecodeError>
244    where
245        T: DeserializeOwned,
246    {
247        serde_json::from_value(value)
248            .map_err(|error| JsonDecodeError::deserialize(error, Some(input_bytes)))
249    }
250
251    /// Maps one `serde_json` error from direct typed decoding to the crate
252    /// error model.
253    ///
254    /// Syntax, EOF, and I/O categories are treated as invalid JSON input.
255    /// Data category errors are treated as type deserialization failures.
256    fn map_decode_error(error: serde_json::Error, input_bytes: usize) -> JsonDecodeError {
257        match error.classify() {
258            Category::Data => JsonDecodeError::deserialize(error, Some(input_bytes)),
259            Category::Io | Category::Syntax | Category::Eof => {
260                JsonDecodeError::invalid_json(error, Some(input_bytes))
261            }
262        }
263    }
264}