Skip to main content

qubit_json/value/traverse/
json_tree_budget_tracker.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//! Provides reusable full-tree JSON budget accounting.
9
10use qubit_budget::MeasuredBudgetError;
11use qubit_budget::ResourceQuantity;
12use qubit_budget::json::JsonResource;
13use qubit_budget::json::JsonValueBudget;
14use qubit_budget::json::JsonValueLimits;
15use serde_json::Value;
16
17use super::JsonTreeReader;
18
19/// Fully accounts materialized JSON trees using an internally owned budget.
20///
21/// # Type Parameters
22///
23/// * `R` - Resource identity tracked by the owned value budget.
24/// * `Q` - Quantity representation used for resource accounting.
25///
26/// # Examples
27///
28/// ```
29/// use qubit_budget::json::JsonResource;
30/// use qubit_budget::json::JsonValueLimits;
31/// use qubit_json::value::traverse::JsonTreeBudgetTracker;
32/// use serde_json::json;
33///
34/// let mut tracker = JsonTreeBudgetTracker::<JsonResource, usize>::new(
35///     JsonValueLimits::default(),
36/// );
37/// assert!(tracker.account(&json!({"ok": true})).is_ok());
38/// # Ok::<(), qubit_budget::MeasuredBudgetError<JsonResource, usize>>(())
39/// ```
40pub struct JsonTreeBudgetTracker<R = JsonResource, Q = usize>
41where
42    Q: ResourceQuantity,
43{
44    /// Internally owned budget accumulating successful tree-accounting runs.
45    budget: JsonValueBudget<R, Q>,
46}
47
48impl<R, Q> JsonTreeBudgetTracker<R, Q>
49where
50    R: Clone,
51    Q: ResourceQuantity,
52{
53    /// Creates a full-tree tracker with fresh budget state.
54    ///
55    /// # Parameters
56    ///
57    /// * `limits` - Resource limits used by the owned budget.
58    ///
59    /// # Returns
60    ///
61    /// A tracker initialized with the supplied limits.
62    #[inline]
63    #[must_use]
64    pub fn new(limits: JsonValueLimits<R, Q>) -> Self {
65        Self {
66            budget: JsonValueBudget::new(limits),
67        }
68    }
69
70    /// Returns the owned budget for read-only inspection.
71    ///
72    /// # Returns
73    ///
74    /// A shared reference to the accumulated budget state.
75    #[must_use]
76    #[inline(always)]
77    pub const fn budget(&self) -> &JsonValueBudget<R, Q> {
78        &self.budget
79    }
80
81    /// Consumes this tracker and returns its accumulated budget state.
82    ///
83    /// # Returns
84    ///
85    /// The owned budget, including all charges accumulated by this tracker.
86    #[must_use]
87    #[inline(always)]
88    pub fn into_budget(self) -> JsonValueBudget<R, Q> {
89        self.budget
90    }
91
92    /// Returns the owned budget for caller-managed accounting.
93    ///
94    /// # Returns
95    ///
96    /// A mutable reference to the accumulated budget state.
97    #[must_use]
98    #[inline(always)]
99    pub fn budget_mut(&mut self) -> &mut JsonValueBudget<R, Q> {
100        &mut self.budget
101    }
102
103    /// Charges every node and payload represented by `value`.
104    ///
105    /// # Parameters
106    ///
107    /// * `value` - JSON tree whose resources are charged.
108    ///
109    /// # Returns
110    ///
111    /// `Ok(())` when the complete tree is admitted.
112    ///
113    /// # Errors
114    ///
115    /// Returns the first measured budget rejection encountered while walking
116    /// the tree. Charges are committed only when the complete walk succeeds.
117    pub fn account(&mut self, value: &Value) -> Result<(), MeasuredBudgetError<R, Q>> {
118        let mut transaction = self.budget.transaction();
119        let result = JsonTreeReader::new(&mut transaction).account(value);
120        match result {
121            Ok(()) => transaction.commit(),
122            Err(error) => Err(error),
123        }
124    }
125
126    /// Restores the owned budget to its original configured state.
127    ///
128    /// This clears accumulated charges and makes the tracker ready for a new
129    /// independent accounting run.
130    ///
131    /// Resetting does not change the configured limits or resource identities;
132    /// it only discards usage accumulated since construction or the previous
133    /// reset.
134    #[inline(always)]
135    pub fn reset(&mut self) {
136        self.budget.reset();
137    }
138}