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