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 ///
58 /// # Parameters
59 ///
60 /// - `text`: The normalized JSON text retained for later decoding.
61 /// - `raw_input_bytes`: The original input length in bytes, used for
62 /// raw-input budget accounting.
63 #[inline]
64 #[must_use]
65 pub(in crate::decode) fn new(text: Cow<'input, str>, raw_input_bytes: usize) -> Self {
66 let normalized_input_bytes = text.len();
67 Self {
68 text,
69 raw_input_bytes,
70 normalized_input_bytes,
71 }
72 }
73
74 /// Returns the normalized JSON text retained by this document.
75 ///
76 /// The returned slice borrows the document. It is the exact text consumed
77 /// by later document-based decoding and does not allocate.
78 ///
79 /// # Returns
80 ///
81 /// The normalized JSON text.
82 #[inline(always)]
83 #[must_use]
84 pub fn as_str(&self) -> &str {
85 self.text.as_ref()
86 }
87
88 /// Returns the original input length in bytes.
89 ///
90 /// This value includes whitespace, a UTF-8 byte-order mark, and any other
91 /// input bytes removed or rewritten during normalization.
92 ///
93 /// # Returns
94 ///
95 /// The byte length charged for the original input.
96 #[inline(always)]
97 #[must_use]
98 pub const fn raw_input_bytes(&self) -> usize {
99 self.raw_input_bytes
100 }
101
102 /// Returns the normalized text length in bytes.
103 ///
104 /// This is the byte length of [`Self::as_str`], after all enabled
105 /// normalization has completed.
106 ///
107 /// # Returns
108 ///
109 /// The byte length charged for normalized text.
110 #[inline(always)]
111 #[must_use]
112 pub const fn normalized_input_bytes(&self) -> usize {
113 self.normalized_input_bytes
114 }
115}