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}