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 the immutable normalization and diagnostic policy.
133 ///
134 /// The returned reference remains tied to this decoder and controls how
135 /// future input is normalized and how input-derived failures are retained.
136 ///
137 /// # Returns
138 ///
139 /// A shared reference to the configured policy.
140 #[inline(always)]
141 pub const fn policy(&self) -> &NormalizingJsonDecodePolicy {
142 self.normalizer.policy()
143 }
144
145 /// Returns mutable access to the cumulative session.
146 ///
147 /// Mutating the session changes the limits and accounting state used by
148 /// subsequent operations.
149 ///
150 /// # Returns
151 ///
152 /// A mutable reference to the cumulative session.
153 #[inline(always)]
154 #[must_use]
155 pub const fn session_mut(&mut self) -> &mut JsonDecodeSession<'budget, R, Q> {
156 self.engine.session_mut()
157 }
158
159 /// Consumes the decoder and returns its cumulative session.
160 ///
161 /// Ownership of all accumulated accounting state is transferred without
162 /// resetting it or performing another decode.
163 ///
164 /// # Returns
165 ///
166 /// The session previously owned by this decoder.
167 #[inline(always)]
168 #[must_use]
169 pub fn into_session(self) -> JsonDecodeSession<'budget, R, Q> {
170 self.engine.into_session()
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}