Skip to main content

qubit_json/value/traverse/
json_tree_reader.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//! Implements non-recursive, read-only JSON tree processing.
9
10use qubit_budget::MeasuredBudgetError;
11use qubit_budget::ResourceQuantity;
12use qubit_budget::json::JsonMeasurement;
13use qubit_budget::json::JsonValueTransaction;
14use serde_json::Value;
15
16use super::JsonTreeContext;
17use super::JsonTreeLocation;
18use super::JsonTreeProcessError;
19use super::JsonTreeVisitor;
20use super::internal::ChildCursor;
21use super::internal::NoopVisitor;
22use super::internal::ReadFrame;
23use super::internal::ReadFrameState;
24use crate::value::internal::json_value_measurement;
25
26/// Processes JSON values while borrowing one staged JSON value transaction.
27///
28/// # Type Parameters
29///
30/// * `R` - Resource identity tracked by the borrowed transaction.
31/// * `Q` - Quantity representation used for resource accounting.
32///
33/// # Examples
34///
35/// ```
36/// use qubit_budget::json::{JsonResource, JsonValueBudget, JsonValueLimits};
37/// use qubit_json::value::traverse::{
38///     JsonTreeContext, JsonTreeReader, JsonTreeVisitor,
39/// };
40/// use serde_json::Value;
41///
42/// struct Visitor;
43/// impl JsonTreeVisitor for Visitor {
44///     type Error = std::convert::Infallible;
45///
46///     fn enter(
47///         &mut self,
48///         _: &Value,
49///         _: JsonTreeContext<'_>,
50///     ) -> Result<(), Self::Error> {
51///         Ok(())
52///     }
53/// }
54///
55/// let mut budget = JsonValueBudget::new(
56///     JsonValueLimits::<JsonResource, usize>::default(),
57/// );
58/// let mut transaction = budget.transaction();
59/// let mut reader = JsonTreeReader::new(&mut transaction);
60/// assert!(reader.process(&Value::Null, &mut Visitor).is_ok());
61/// ```
62pub struct JsonTreeReader<'transaction, 'budget, R, Q>
63where
64    Q: ResourceQuantity,
65{
66    /// Transaction receiving staged node and payload charges.
67    transaction: &'transaction mut JsonValueTransaction<'budget, R, Q>,
68    /// Whether any admission check can reject this traversal.
69    enforce_limits: bool,
70}
71
72impl<'transaction, 'budget, R, Q> JsonTreeReader<'transaction, 'budget, R, Q>
73where
74    R: Clone,
75    Q: ResourceQuantity,
76{
77    /// Creates a reader borrowing the supplied JSON value transaction.
78    ///
79    /// # Parameters
80    ///
81    /// * `transaction` - Transaction receiving node and payload charges.
82    ///
83    /// # Returns
84    ///
85    /// A reader borrowing `transaction` for its lifetime.
86    #[inline(always)]
87    #[must_use]
88    pub fn new(transaction: &'transaction mut JsonValueTransaction<'budget, R, Q>) -> Self {
89        let enforce_limits = transaction.has_limits();
90        Self {
91            transaction,
92            enforce_limits,
93        }
94    }
95
96    /// Processes every node in depth-first order without Rust recursion.
97    ///
98    /// # Type Parameters
99    ///
100    /// * `V` - Visitor receiving admitted-node callbacks.
101    ///
102    /// # Parameters
103    ///
104    /// * `value` - Root JSON value to process.
105    /// * `visitor` - Visitor invoked around each admitted node.
106    ///
107    /// # Returns
108    ///
109    /// `Ok(())` after the complete tree is processed.
110    ///
111    /// # Errors
112    ///
113    /// Returns [`JsonTreeProcessError::Budget`] when resource admission fails,
114    /// or [`JsonTreeProcessError::Visitor`] when the visitor rejects a node.
115    pub fn process<V>(&mut self, value: &Value, visitor: &mut V) -> Result<(), JsonTreeProcessError<R, Q, V::Error>>
116    where
117        V: JsonTreeVisitor,
118    {
119        let mut pending = vec![ReadFrame::enter(
120            value,
121            JsonTreeContext {
122                depth: 1,
123                location: JsonTreeLocation::Root,
124            },
125        )];
126        while let Some(frame) = pending.last_mut() {
127            match &mut frame.state {
128                ReadFrameState::Enter => {
129                    let value = frame.value;
130                    let context = frame.context;
131                    if self.enforce_limits {
132                        if let JsonTreeLocation::ObjectValue { key } = context.location {
133                            self.transaction.try_admit(JsonMeasurement::Key { bytes: key.len() })?;
134                        }
135                        self.admit(value, context.depth)?;
136                    }
137                    visitor.enter(value, context).map_err(JsonTreeProcessError::Visitor)?;
138                    frame.state = ReadFrameState::Children(ChildCursor::new(value, context.depth));
139                }
140                ReadFrameState::Children(cursor) => {
141                    if let Some((value, location, depth)) = cursor.next() {
142                        pending.push(ReadFrame::enter(value, JsonTreeContext { depth, location }));
143                    } else {
144                        frame.state = ReadFrameState::Leave;
145                    }
146                }
147                ReadFrameState::Leave => {
148                    let frame = pending.pop().expect("read frame exists");
149                    visitor
150                        .leave(frame.value, frame.context)
151                        .map_err(JsonTreeProcessError::Visitor)?;
152                }
153            }
154        }
155        Ok(())
156    }
157
158    /// Accounts every node and payload without invoking a domain visitor.
159    ///
160    /// The charges remain staged in the borrowed transaction. The caller
161    /// decides whether to commit it after any surrounding work succeeds.
162    ///
163    /// # Parameters
164    ///
165    /// * `value` - Root JSON value whose complete tree is admitted.
166    ///
167    /// # Returns
168    ///
169    /// `Ok(())` after every node and payload has been staged in the borrowed
170    /// transaction.
171    ///
172    /// # Errors
173    ///
174    /// Returns the first measured budget rejection encountered during the
175    /// traversal.
176    pub fn account(&mut self, value: &Value) -> Result<(), MeasuredBudgetError<R, Q>> {
177        if !self.enforce_limits {
178            return Ok(());
179        }
180        match self.process(value, &mut NoopVisitor) {
181            Ok(()) => Ok(()),
182            Err(JsonTreeProcessError::Budget(error)) => Err(error),
183            Err(JsonTreeProcessError::Visitor(error)) => match error {},
184        }
185    }
186
187    /// Admits one node before any visitor callback.
188    fn admit(&mut self, value: &Value, depth: usize) -> Result<(), MeasuredBudgetError<R, Q>> {
189        self.transaction.try_admit(json_value_measurement(value, depth))
190    }
191}