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}