Skip to main content

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}