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}