qubit_json/decode/normalized_json_document.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines normalized JSON text retained for borrowing deserialization.
9
10use std::borrow::Cow;
11
12/// A normalized JSON document whose text outlives borrowed decode results.
13///
14/// Preparing a document charges raw and normalized input budgets. Decoding it
15/// later charges only decoded-value budgets, and the same document may be
16/// decoded repeatedly. Documents are detached from the decoder that prepared
17/// them; value charges belong to the decoder performing each decode.
18///
19/// Borrowing follows Serde's JSON representation rules: strings without JSON
20/// escapes can borrow from this document, while strings containing escapes
21/// require an owned target because deserialization must materialize their
22/// unescaped contents.
23///
24/// # Examples
25///
26/// ```
27/// use qubit_budget::json::JsonDecodeLimits;
28/// use qubit_json::decode::NormalizingJsonDecodePolicy;
29/// use qubit_json::decode::NormalizingJsonDecoder;
30/// use serde_json::Value;
31///
32/// let input = " {\"ok\":true} ";
33/// let mut decoder = NormalizingJsonDecoder::with_limits(
34/// NormalizingJsonDecodePolicy::builder().build(),
35/// JsonDecodeLimits::new(),
36/// );
37/// let document = decoder.prepare_str(input)?;
38/// assert_eq!(document.as_str(), r#"{"ok":true}"#);
39/// assert_eq!(document.raw_input_bytes(), input.len());
40/// assert_eq!(document.normalized_input_bytes(), document.as_str().len());
41/// let value = decoder.decode_precharged_document::<Value>(&document)?;
42/// assert_eq!(value["ok"], true);
43/// # Ok::<(), qubit_json::decode::JsonDecodeError>(())
44/// ```
45#[derive(Debug, Clone)]
46pub struct NormalizedJsonDocument<'input> {
47 /// Normalized text, borrowed when rewriting did not require allocation.
48 text: Cow<'input, str>,
49 /// Original input length in bytes.
50 raw_input_bytes: usize,
51 /// Normalized text length in bytes.
52 normalized_input_bytes: usize,
53}
54
55impl<'input> NormalizedJsonDocument<'input> {
56 /// Creates a document from normalized text and its original byte length.
57 #[inline]
58 #[must_use]
59 pub(in crate::decode) fn new(text: Cow<'input, str>, raw_input_bytes: usize) -> Self {
60 let normalized_input_bytes = text.len();
61 Self {
62 text,
63 raw_input_bytes,
64 normalized_input_bytes,
65 }
66 }
67
68 /// Returns the normalized JSON text retained by this document.
69 ///
70 /// The returned slice borrows the document. It is the exact text consumed
71 /// by later document-based decoding and does not allocate.
72 ///
73 /// # Returns
74 ///
75 /// The normalized JSON text.
76 #[inline(always)]
77 #[must_use]
78 pub fn as_str(&self) -> &str {
79 self.text.as_ref()
80 }
81
82 /// Returns the original input length in bytes.
83 ///
84 /// This value includes whitespace, a UTF-8 byte-order mark, and any other
85 /// input bytes removed or rewritten during normalization.
86 ///
87 /// # Returns
88 ///
89 /// The byte length charged for the original input.
90 #[inline(always)]
91 #[must_use]
92 pub const fn raw_input_bytes(&self) -> usize {
93 self.raw_input_bytes
94 }
95
96 /// Returns the normalized text length in bytes.
97 ///
98 /// This is the byte length of [`Self::as_str`], after all enabled
99 /// normalization has completed.
100 ///
101 /// # Returns
102 ///
103 /// The byte length charged for normalized text.
104 #[inline(always)]
105 #[must_use]
106 pub const fn normalized_input_bytes(&self) -> usize {
107 self.normalized_input_bytes
108 }
109}