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>(&self, input: &str, expected: JsonTopLevelKind) -> Result<T, JsonDecodeError>
201 where
202 T: DeserializeOwned,
203 {
204 let (value, input_bytes) = self.parse_input_as_value(input)?;
205 Self::ensure_top_level_from_value(&value, expected)?;
206 Self::deserialize_value(value, input_bytes)
207 }
208
209 /// Parses normalized text into a JSON value.
210 ///
211 /// Syntax failures are mapped to the crate error model with normalized
212 /// input byte length included for diagnostics.
213 fn parse_value(normalized: &str) -> Result<Value, JsonDecodeError> {
214 serde_json::from_str(normalized).map_err(|error| JsonDecodeError::invalid_json(error, Some(normalized.len())))
215 }
216
217 /// Verifies that a parsed JSON value has the required top-level kind.
218 fn ensure_top_level_from_value(value: &Value, expected: JsonTopLevelKind) -> Result<(), JsonDecodeError> {
219 let actual = JsonTopLevelKind::of(value);
220 if actual != expected {
221 return Err(JsonDecodeError::unexpected_top_level(expected, actual));
222 }
223 Ok(())
224 }
225
226 /// Deserializes normalized JSON text into the target type.
227 fn deserialize_normalized<T>(normalized: &str, input_bytes: usize) -> Result<T, JsonDecodeError>
228 where
229 T: DeserializeOwned,
230 {
231 serde_json::from_str(normalized).map_err(|error| Self::map_decode_error(error, input_bytes))
232 }
233
234 /// Deserializes a parsed JSON value into the target type.
235 fn deserialize_value<T>(value: Value, input_bytes: usize) -> Result<T, JsonDecodeError>
236 where
237 T: DeserializeOwned,
238 {
239 serde_json::from_value(value).map_err(|error| JsonDecodeError::deserialize(error, Some(input_bytes)))
240 }
241
242 /// Maps one `serde_json` error from direct typed decoding to the crate
243 /// error model.
244 ///
245 /// Syntax, EOF, and I/O categories are treated as invalid JSON input.
246 /// Data category errors are treated as type deserialization failures.
247 fn map_decode_error(error: serde_json::Error, input_bytes: usize) -> JsonDecodeError {
248 match error.classify() {
249 Category::Data => JsonDecodeError::deserialize(error, Some(input_bytes)),
250 Category::Io | Category::Syntax | Category::Eof => JsonDecodeError::invalid_json(error, Some(input_bytes)),
251 }
252 }
253}