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