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}