Skip to main content

qubit_json/decode/
normalizing_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 explicit normalization facade for JSON decoding.
9
10use qubit_budget::ResourceQuantity;
11use qubit_budget::json::JsonDecodeLimits;
12use qubit_budget::json::JsonDecodeSession;
13use qubit_budget::json::JsonResource;
14use serde::Deserialize;
15use serde::de::DeserializeOwned;
16use serde::de::DeserializeSeed;
17use serde_json::Value;
18
19use super::JsonDecodeError;
20use super::JsonRootKind;
21use super::NormalizedJsonDocument;
22use super::NormalizingJsonDecodePolicy;
23use super::internal::JsonDecodeEngine;
24use super::internal::JsonNormalizer;
25use super::internal::TypedSeed;
26
27/// Normalizes non-fully-trusted text before decoding complete JSON documents.
28///
29/// Owned convenience methods prepare and decode in one call. Callers needing
30/// borrowed results, custom seeds, or repeated materialization first create a
31/// [`NormalizedJsonDocument`] and then use a document decoding method.
32///
33/// # Type Parameters
34///
35/// * `'budget` - Lifetime of the cumulative session borrowed by this decoder.
36/// * `R` - Resource kind tracked by the decoding session.
37/// * `Q` - Quantity type used for resource accounting.
38///
39/// # Examples
40///
41/// ```
42/// use qubit_budget::json::JsonDecodeLimits;
43/// use qubit_json::decode::NormalizingJsonDecodePolicy;
44/// use qubit_json::decode::NormalizingJsonDecoder;
45/// use serde_json::Value;
46///
47/// let mut decoder = NormalizingJsonDecoder::with_limits(
48///     NormalizingJsonDecodePolicy::builder().build(),
49///     JsonDecodeLimits::new(),
50/// );
51/// let value = decoder.decode_str::<Value>("```json\n{\"ok\":true}\n```")?;
52/// assert_eq!(value["ok"], true);
53/// # Ok::<(), qubit_json::decode::JsonDecodeError>(())
54/// ```
55#[derive(Debug)]
56pub struct NormalizingJsonDecoder<'budget, R = JsonResource, Q = usize>
57where
58    Q: ResourceQuantity,
59{
60    /// Configured normalization pipeline.
61    normalizer: JsonNormalizer,
62    /// Shared generic decoding and accounting core.
63    engine: JsonDecodeEngine<'budget, R, Q>,
64}
65
66impl<R, Q> NormalizingJsonDecoder<'static, R, Q>
67where
68    R: Clone,
69    Q: ResourceQuantity,
70{
71    /// Creates a normalizing decoder with a cumulative session built from
72    /// explicit limits.
73    ///
74    /// # Parameters
75    ///
76    /// * `policy` - Normalization and diagnostic behavior applied before
77    ///   decoding.
78    /// * `limits` - Input and decoded-value limits used by the cumulative
79    ///   session.
80    ///
81    /// # Returns
82    ///
83    /// A decoder whose accounting starts empty and is constrained by `limits`.
84    #[inline]
85    #[must_use]
86    pub fn with_limits(
87        policy: NormalizingJsonDecodePolicy,
88        limits: JsonDecodeLimits<R, Q>,
89    ) -> Self {
90        Self::new(policy, JsonDecodeSession::from_limits(limits))
91    }
92}
93
94impl<'budget, R, Q> NormalizingJsonDecoder<'budget, R, Q>
95where
96    R: Clone,
97    Q: ResourceQuantity,
98{
99    /// Creates a decoder around a reusable caller-provided session.
100    ///
101    /// # Parameters
102    ///
103    /// * `policy` - Normalization and diagnostic behavior applied before
104    ///   decoding.
105    /// * `session` - Cumulative session receiving input and decoded-value
106    ///   charges.
107    ///
108    /// # Returns
109    ///
110    /// A decoder that owns `session` until it is consumed by
111    /// [`Self::into_session`].
112    #[inline]
113    #[must_use]
114    pub const fn new(
115        policy: NormalizingJsonDecodePolicy,
116        session: JsonDecodeSession<'budget, R, Q>,
117    ) -> Self {
118        Self {
119            normalizer: JsonNormalizer::new(policy),
120            engine: JsonDecodeEngine::new(session),
121        }
122    }
123
124    /// Returns the cumulative session for read-only inspection.
125    ///
126    /// The returned reference exposes charges accumulated by completed
127    /// preparation and decoding operations.
128    ///
129    /// # Returns
130    ///
131    /// A shared reference to the cumulative session.
132    #[inline(always)]
133    #[must_use]
134    pub const fn session(&self) -> &JsonDecodeSession<'budget, R, Q> {
135        self.engine.session()
136    }
137
138    /// Returns the immutable normalization and diagnostic policy.
139    ///
140    /// The returned reference remains tied to this decoder and controls how
141    /// future input is normalized and how input-derived failures are retained.
142    ///
143    /// # Returns
144    ///
145    /// A shared reference to the configured policy.
146    #[inline(always)]
147    pub const fn policy(&self) -> &NormalizingJsonDecodePolicy {
148        self.normalizer.policy()
149    }
150
151    /// Returns mutable access to the cumulative session.
152    ///
153    /// Mutating the session changes the limits and accounting state used by
154    /// subsequent operations.
155    ///
156    /// # Returns
157    ///
158    /// A mutable reference to the cumulative session.
159    #[inline(always)]
160    #[must_use]
161    pub const fn session_mut(&mut self) -> &mut JsonDecodeSession<'budget, R, Q> {
162        self.engine.session_mut()
163    }
164
165    /// Consumes the decoder and returns its cumulative session.
166    ///
167    /// Ownership of all accumulated accounting state is transferred without
168    /// resetting it or performing another decode.
169    ///
170    /// # Returns
171    ///
172    /// The session previously owned by this decoder.
173    #[inline(always)]
174    #[must_use]
175    pub fn into_session(self) -> JsonDecodeSession<'budget, R, Q> {
176        self.engine.into_session()
177    }
178
179    /// Normalizes one string and immediately charges its input budgets.
180    ///
181    /// The returned document may borrow `input`. Later document decoding does
182    /// not charge its input again and commits only decoded-value usage.
183    ///
184    /// # Parameters
185    ///
186    /// * `input` - JSON text to normalize and charge.
187    ///
188    /// # Returns
189    ///
190    /// A normalized document that may borrow `input`.
191    ///
192    /// # Errors
193    ///
194    /// Returns a structured error when input accounting, UTF-8 validation, or
195    /// normalization fails.
196    pub fn prepare_str<'input>(
197        &mut self,
198        input: &'input str,
199    ) -> Result<NormalizedJsonDocument<'input>, JsonDecodeError<R, Q>> {
200        self.engine.prepare_str(&self.normalizer, input)
201    }
202
203    /// Charges raw bytes, validates UTF-8, and normalizes one byte slice.
204    ///
205    /// Raw input usage remains charged when UTF-8 validation or normalization
206    /// fails. The returned document may borrow the original byte slice.
207    ///
208    /// # Parameters
209    ///
210    /// * `input` - UTF-8 byte slice to validate, normalize, and charge.
211    ///
212    /// # Returns
213    ///
214    /// A normalized document that may borrow `input`.
215    ///
216    /// # Errors
217    ///
218    /// Returns a structured error when input accounting, UTF-8 validation, or
219    /// normalization fails.
220    pub fn prepare_utf8<'input>(
221        &mut self,
222        input: &'input [u8],
223    ) -> Result<NormalizedJsonDocument<'input>, JsonDecodeError<R, Q>> {
224        self.engine.prepare_utf8(&self.normalizer, input)
225    }
226
227    /// Decodes one precharged document and permits results borrowing it.
228    ///
229    /// Preparing the document has already committed its raw and normalized
230    /// input usage. This method commits only the decoded-value usage.
231    ///
232    /// # Type Parameters
233    ///
234    /// * `T` - Target type deserialized from the prepared document.
235    ///
236    /// # Parameters
237    ///
238    /// * `document` - Precharged normalized document that outlives the returned
239    ///   value.
240    ///
241    /// # Returns
242    ///
243    /// The deserialized value on success.
244    ///
245    /// # Errors
246    ///
247    /// Returns a structured error when decoded-value accounting, parsing, or
248    /// deserialization fails.
249    pub fn decode_precharged_document<'de, T>(
250        &mut self,
251        document: &'de NormalizedJsonDocument<'_>,
252    ) -> Result<T, JsonDecodeError<R, Q>>
253    where
254        T: Deserialize<'de>,
255    {
256        self.decode_precharged_document_seed(document, TypedSeed::new())
257    }
258
259    /// Decodes one precharged document through a caller-provided Serde seed.
260    ///
261    /// Preparing the document has already committed its raw and normalized
262    /// input usage. This method commits only the decoded-value usage.
263    ///
264    /// # Type Parameters
265    ///
266    /// * `S` - Seed controlling construction of the decoded value.
267    ///
268    /// # Parameters
269    ///
270    /// * `document` - Precharged normalized document.
271    /// * `seed` - Serde seed used to deserialize the document.
272    ///
273    /// # Returns
274    ///
275    /// The value produced by `seed`.
276    ///
277    /// # Errors
278    ///
279    /// Returns a structured error when decoded-value accounting, parsing, or
280    /// seeded deserialization fails.
281    pub fn decode_precharged_document_seed<'de, S>(
282        &mut self,
283        document: &'de NormalizedJsonDocument<'_>,
284        seed: S,
285    ) -> Result<S::Value, JsonDecodeError<R, Q>>
286    where
287        S: DeserializeSeed<'de>,
288    {
289        self.engine
290            .decode_document_seed(document, seed, self.policy().diagnostic_policy(), None)
291    }
292
293    /// Decodes one precharged document while requiring a top-level object.
294    ///
295    /// Preparing the document has already committed its raw and normalized
296    /// input usage. This method commits only the decoded-value usage.
297    ///
298    /// # Type Parameters
299    ///
300    /// * `T` - Target type deserialized from the object document.
301    ///
302    /// # Parameters
303    ///
304    /// * `document` - Precharged document that outlives the returned value.
305    ///
306    /// # Returns
307    ///
308    /// The deserialized object value on success.
309    ///
310    /// # Errors
311    ///
312    /// Returns a structured error for accounting, parsing, top-level-kind, or
313    /// deserialization failures.
314    pub fn decode_precharged_object_document<'de, T>(
315        &mut self,
316        document: &'de NormalizedJsonDocument<'_>,
317    ) -> Result<T, JsonDecodeError<R, Q>>
318    where
319        T: Deserialize<'de>,
320    {
321        self.engine.decode_document_seed(
322            document,
323            TypedSeed::new(),
324            self.policy().diagnostic_policy(),
325            Some(JsonRootKind::Object),
326        )
327    }
328
329    /// Decodes one precharged document while requiring a top-level array.
330    ///
331    /// Preparing the document has already committed its raw and normalized
332    /// input usage. This method commits only the decoded-value usage.
333    ///
334    /// # Type Parameters
335    ///
336    /// * `T` - Element type deserialized from the array.
337    ///
338    /// # Parameters
339    ///
340    /// * `document` - Precharged document that outlives the returned elements.
341    ///
342    /// # Returns
343    ///
344    /// The decoded array elements on success.
345    ///
346    /// # Errors
347    ///
348    /// Returns a structured error for accounting, parsing, top-level-kind, or
349    /// deserialization failures.
350    pub fn decode_precharged_array_document<'de, T>(
351        &mut self,
352        document: &'de NormalizedJsonDocument<'_>,
353    ) -> Result<Vec<T>, JsonDecodeError<R, Q>>
354    where
355        T: Deserialize<'de>,
356    {
357        self.engine.decode_document_seed(
358            document,
359            TypedSeed::new(),
360            self.policy().diagnostic_policy(),
361            Some(JsonRootKind::Array),
362        )
363    }
364
365    /// Validates a precharged document and commits its decoded-value usage.
366    ///
367    /// Preparing the document has already committed its raw and normalized
368    /// input usage. This method commits only the decoded-value usage.
369    ///
370    /// # Parameters
371    ///
372    /// * `document` - Precharged document whose JSON syntax is validated.
373    ///
374    /// # Returns
375    ///
376    /// `Ok(())` after the normalized document is valid and its decoded-value
377    /// usage is committed.
378    ///
379    /// # Errors
380    ///
381    /// Returns a structured error when decoded-value accounting or JSON
382    /// validation fails.
383    pub fn validate_precharged_document(
384        &mut self,
385        document: &NormalizedJsonDocument<'_>,
386    ) -> Result<(), JsonDecodeError<R, Q>> {
387        self.engine
388            .validate_document(document, self.policy().diagnostic_policy(), None)
389    }
390
391    /// Normalizes and decodes one string into an owned target value.
392    ///
393    /// # Type Parameters
394    ///
395    /// * `T` - Owned target type deserialized from the normalized document.
396    ///
397    /// # Parameters
398    ///
399    /// * `input` - JSON text to normalize and decode.
400    ///
401    /// # Returns
402    ///
403    /// The owned deserialized value on success.
404    ///
405    /// # Errors
406    ///
407    /// Returns a structured error when normalization, accounting, parsing, or
408    /// deserialization fails.
409    pub fn decode_str<T>(&mut self, input: &str) -> Result<T, JsonDecodeError<R, Q>>
410    where
411        T: DeserializeOwned,
412    {
413        let document = self.prepare_str(input)?;
414        self.decode_precharged_document(&document)
415    }
416
417    /// Normalizes and decodes one UTF-8 byte slice into an owned target value.
418    ///
419    /// # Type Parameters
420    ///
421    /// * `T` - Owned target type deserialized from the normalized document.
422    ///
423    /// # Parameters
424    ///
425    /// * `input` - UTF-8 JSON bytes to normalize and decode.
426    ///
427    /// # Returns
428    ///
429    /// The owned deserialized value on success.
430    ///
431    /// # Errors
432    ///
433    /// Returns a structured error when normalization, accounting, UTF-8
434    /// validation, parsing, or deserialization fails.
435    pub fn decode_utf8<T>(&mut self, input: &[u8]) -> Result<T, JsonDecodeError<R, Q>>
436    where
437        T: DeserializeOwned,
438    {
439        let document = self.prepare_utf8(input)?;
440        self.decode_precharged_document(&document)
441    }
442
443    /// Normalizes and decodes one string while requiring a top-level object.
444    ///
445    /// # Type Parameters
446    ///
447    /// * `T` - Owned object type deserialized from the normalized document.
448    ///
449    /// # Parameters
450    ///
451    /// * `input` - JSON text whose normalized root must be an object.
452    ///
453    /// # Returns
454    ///
455    /// The owned deserialized object value on success.
456    ///
457    /// # Errors
458    ///
459    /// Returns a structured error for normalization, accounting, parsing,
460    /// top-level-kind, or deserialization failures.
461    pub fn decode_object_str<T>(&mut self, input: &str) -> Result<T, JsonDecodeError<R, Q>>
462    where
463        T: DeserializeOwned,
464    {
465        let document = self.prepare_str(input)?;
466        self.decode_precharged_object_document(&document)
467    }
468
469    /// Normalizes and decodes one UTF-8 byte slice while requiring a top-level
470    /// object.
471    ///
472    /// # Type Parameters
473    ///
474    /// * `T` - Owned object type deserialized from the normalized document.
475    ///
476    /// # Parameters
477    ///
478    /// * `input` - UTF-8 JSON bytes whose normalized root must be an object.
479    ///
480    /// # Returns
481    ///
482    /// The owned deserialized object value on success.
483    ///
484    /// # Errors
485    ///
486    /// Returns a structured error for normalization, accounting, UTF-8
487    /// validation, parsing, top-level-kind, or deserialization failures.
488    pub fn decode_object_utf8<T>(&mut self, input: &[u8]) -> Result<T, JsonDecodeError<R, Q>>
489    where
490        T: DeserializeOwned,
491    {
492        let document = self.prepare_utf8(input)?;
493        self.decode_precharged_object_document(&document)
494    }
495
496    /// Normalizes and decodes one string while requiring a top-level array.
497    ///
498    /// # Type Parameters
499    ///
500    /// * `T` - Owned element type deserialized from the normalized document.
501    ///
502    /// # Parameters
503    ///
504    /// * `input` - JSON text whose normalized root must be an array.
505    ///
506    /// # Returns
507    ///
508    /// The owned decoded array elements on success.
509    ///
510    /// # Errors
511    ///
512    /// Returns a structured error for normalization, accounting, parsing,
513    /// top-level-kind, or deserialization failures.
514    pub fn decode_array_str<T>(&mut self, input: &str) -> Result<Vec<T>, JsonDecodeError<R, Q>>
515    where
516        T: DeserializeOwned,
517    {
518        let document = self.prepare_str(input)?;
519        self.decode_precharged_array_document(&document)
520    }
521
522    /// Normalizes and decodes one UTF-8 byte slice while requiring a top-level
523    /// array.
524    ///
525    /// # Type Parameters
526    ///
527    /// * `T` - Owned element type deserialized from the normalized document.
528    ///
529    /// # Parameters
530    ///
531    /// * `input` - UTF-8 JSON bytes whose normalized root must be an array.
532    ///
533    /// # Returns
534    ///
535    /// The owned decoded array elements on success.
536    ///
537    /// # Errors
538    ///
539    /// Returns a structured error for normalization, accounting, UTF-8
540    /// validation, parsing, top-level-kind, or deserialization failures.
541    pub fn decode_array_utf8<T>(&mut self, input: &[u8]) -> Result<Vec<T>, JsonDecodeError<R, Q>>
542    where
543        T: DeserializeOwned,
544    {
545        let document = self.prepare_utf8(input)?;
546        self.decode_precharged_array_document(&document)
547    }
548
549    /// Normalizes and decodes one string into a dynamic JSON value.
550    ///
551    /// # Parameters
552    ///
553    /// * `input` - JSON text to normalize and materialize as a value tree.
554    ///
555    /// # Returns
556    ///
557    /// The materialized JSON value on success.
558    ///
559    /// # Errors
560    ///
561    /// Returns a structured error when normalization, accounting, parsing, or
562    /// value construction fails.
563    #[inline]
564    pub fn decode_value(&mut self, input: &str) -> Result<Value, JsonDecodeError<R, Q>> {
565        self.decode_str(input)
566    }
567}