1use indexmap::IndexSet;
2use serde_json::Value;
3use std::collections::HashMap;
4
5#[derive(Default, Clone)]
7pub struct VersionTracker {
8 versions: HashMap<String, u64>,
9}
10
11impl VersionTracker {
12 pub fn new() -> Self {
13 Self {
14 versions: HashMap::new(),
15 }
16 }
17
18 #[inline]
19 pub fn get(&self, path: &str) -> u64 {
20 self.versions.get(path).copied().unwrap_or(0)
21 }
22
23 #[inline]
24 pub fn bump(&mut self, path: &str, source: &str) {
25 let current = self.get(path);
26 if crate::utils::is_debug_cache_enabled() {
27 println!(
28 "[store_cache] BUMPING for {} -> {} ({})",
29 path,
30 current + 1,
31 source
32 );
33 }
34 self.versions.insert(path.to_string(), current + 1);
36 }
37
38 pub fn merge_from(&mut self, other: &VersionTracker) {
42 for (k, v) in &other.versions {
43 let current = self.versions.get(k).copied().unwrap_or(0);
44 self.versions.insert(k.clone(), current.max(*v));
45 }
46 }
47
48 pub fn merge_from_params(&mut self, other: &VersionTracker) {
52 for (k, v) in &other.versions {
53 if k.starts_with("/$params") {
54 let current = self.versions.get(k).copied().unwrap_or(0);
55 self.versions.insert(k.clone(), current.max(*v));
56 }
57 }
58 }
59
60 pub(crate) fn merge_excluding_prefix(&mut self, other: &VersionTracker, excluded_prefix: &str) {
62 for (k, v) in &other.versions {
63 if !k.starts_with(excluded_prefix) {
64 let current = self.versions.get(k).copied().unwrap_or(0);
65 self.versions.insert(k.clone(), current.max(*v));
66 }
67 }
68 }
69
70 pub fn any_bumped_with_prefix(&self, prefix: &str) -> bool {
73 self.versions
74 .iter()
75 .any(|(k, &v)| k.starts_with(prefix) && v > 0)
76 }
77
78 pub fn any_newly_bumped_with_prefix(&self, prefix: &str, baseline: &VersionTracker) -> bool {
82 self.versions
83 .iter()
84 .any(|(k, &v)| k.starts_with(prefix) && v > baseline.get(k))
85 }
86
87 pub fn versions(&self) -> impl Iterator<Item = (&str, &u64)> {
89 self.versions.iter().map(|(k, v)| (k.as_str(), v))
90 }
91}
92
93#[derive(Clone)]
95pub struct CacheEntry {
96 pub dep_versions: HashMap<String, u64>,
97 pub result: std::sync::Arc<Value>,
98 pub computed_for_item: Option<usize>,
103}
104
105#[derive(Default, Clone)]
107pub struct SubformItemCache {
108 pub data_versions: VersionTracker,
109 pub entries: HashMap<String, CacheEntry>,
110 pub item_snapshot: Value,
111 pub evaluated_schema: Option<Value>,
115}
116
117impl SubformItemCache {
118 pub fn new() -> Self {
119 Self {
120 data_versions: VersionTracker::new(),
121 entries: HashMap::new(),
122 item_snapshot: Value::Null,
123 evaluated_schema: None,
124 }
125 }
126}
127
128#[derive(Clone)]
130pub struct EvalCache {
131 pub data_versions: VersionTracker,
132 pub params_versions: VersionTracker,
133 pub entries: HashMap<String, CacheEntry>,
134
135 pub active_item_index: Option<usize>,
136 pub subform_caches: HashMap<usize, SubformItemCache>,
137
138 pub eval_generation: u64,
142 pub last_evaluated_generation: u64,
143
144 pub main_form_snapshot: Option<std::sync::Arc<Value>>,
148 pub subform_roots: Vec<String>,
149}
150
151impl Default for EvalCache {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl EvalCache {
158 pub fn new() -> Self {
159 Self {
160 data_versions: VersionTracker::new(),
161 params_versions: VersionTracker::new(),
162 entries: HashMap::new(),
163 active_item_index: None,
164 subform_caches: HashMap::new(),
165 eval_generation: 0,
166 last_evaluated_generation: u64::MAX, main_form_snapshot: None,
168 subform_roots: Vec::new(),
169 }
170 }
171
172 pub fn is_subform_dep(&self, path: &str) -> bool {
173 self.subform_roots
174 .iter()
175 .any(|root| path == root || path.starts_with(&format!("{}/", root)))
176 }
177
178 pub fn clear(&mut self) {
179 self.data_versions = VersionTracker::new();
180 self.params_versions = VersionTracker::new();
181 self.entries.clear();
182 self.active_item_index = None;
183 self.subform_caches.clear();
184 self.eval_generation = 0;
185 self.last_evaluated_generation = u64::MAX;
186 self.main_form_snapshot = None;
187 }
188
189 pub fn prune_subform_caches(&mut self, current_count: usize) {
193 self.subform_caches.retain(|&idx, _| idx < current_count);
194 }
195
196 pub fn invalidate_params_tables_for_item(&mut self, idx: usize, table_keys: &[String]) {
203 for key in table_keys {
205 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(key);
206 self.params_versions
207 .bump(&data_path, "invalidate_params_tables_for_item");
208 self.eval_generation += 1;
209 }
210
211 if let Some(item_cache) = self.subform_caches.get_mut(&idx) {
213 for key in table_keys {
214 item_cache.entries.remove(key);
215 }
216 }
217 }
218
219 pub fn needs_full_evaluation(&self) -> bool {
221 self.eval_generation != self.last_evaluated_generation
222 }
223
224 pub fn mark_evaluated(&mut self) {
226 self.last_evaluated_generation = self.eval_generation;
227 }
228
229 pub(crate) fn ensure_active_item_cache(&mut self, idx: usize) {
230 self.subform_caches
231 .entry(idx)
232 .or_insert_with(SubformItemCache::new);
233 }
234
235 pub fn set_active_item(&mut self, idx: usize) {
236 self.active_item_index = Some(idx);
237 self.ensure_active_item_cache(idx);
238 }
239
240 pub fn clear_active_item(&mut self) {
241 self.active_item_index = None;
242 }
243
244 pub fn store_snapshot_and_diff_versions(&mut self, old: &Value, new: &Value) {
246 if let Some(idx) = self.active_item_index {
247 self.ensure_active_item_cache(idx);
248 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
249 diff_and_update_versions(
250 &mut sub_cache.data_versions,
251 "",
252 old,
253 new,
254 "subform store_snapshot_and_diff_versions",
255 );
256 sub_cache.item_snapshot = new.clone();
257 } else {
258 diff_and_update_versions(
259 &mut self.data_versions,
260 "",
261 old,
262 new,
263 "store_snapshot_and_diff_versions",
264 );
265 }
266 }
267
268 pub fn get_active_snapshot(&self) -> Value {
269 if let Some(idx) = self.active_item_index {
270 self.subform_caches
271 .get(&idx)
272 .map(|c| c.item_snapshot.clone())
273 .unwrap_or(Value::Null)
274 } else {
275 Value::Null
276 }
277 }
278
279 pub fn diff_active_item(
280 &mut self,
281 field_key: &str,
282 old_sub_data: &Value,
283 new_sub_data: &Value,
284 ) {
285 if let Some(idx) = self.active_item_index {
286 self.ensure_active_item_cache(idx);
287 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
288
289 let empty = Value::Null;
291 let old_item = old_sub_data.get(field_key).unwrap_or(&empty);
292 let new_item = new_sub_data.get(field_key).unwrap_or(&empty);
293
294 diff_and_update_versions(
295 &mut sub_cache.data_versions,
296 &format!("/{}", field_key),
297 old_item,
298 new_item,
299 format!("diff_active_item {}", field_key).as_str(),
300 );
301 sub_cache.item_snapshot = new_sub_data.clone();
302 }
303 }
304
305 pub fn bump_data_version(&mut self, data_path: &str) {
306 self.eval_generation += 1;
309 if let Some(idx) = self.active_item_index {
310 if let Some(cache) = self.subform_caches.get_mut(&idx) {
311 cache.data_versions.bump(data_path, "bump_data_version1");
312 }
313 } else {
314 self.data_versions.bump(data_path, "bump_data_version2");
315 }
316 }
317
318 pub fn bump_params_version(&mut self, data_path: &str) {
319 self.params_versions.bump(data_path, "bump_params_version");
320 self.eval_generation += 1;
321 }
322
323 pub fn check_cache(&self, eval_key: &str, deps: &IndexSet<String>) -> Option<Value> {
329 self.check_cache_arc(eval_key, deps)
330 .map(|arc| (*arc).clone())
331 }
332
333 pub fn check_cache_arc(
334 &self,
335 eval_key: &str,
336 deps: &IndexSet<String>,
337 ) -> Option<std::sync::Arc<Value>> {
338 if let Some(idx) = self.active_item_index {
339 if let Some(cache) = self.subform_caches.get(&idx) {
341 if let Some(hit) =
342 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
343 {
344 if crate::utils::is_debug_cache_enabled() {
345 println!("Cache HIT [T1 idx={}] {}", idx, eval_key);
346 }
347 return Some(hit);
348 }
349 }
350
351 let item_data_versions = self
353 .subform_caches
354 .get(&idx)
355 .map(|c| &c.data_versions)
356 .unwrap_or(&self.data_versions);
357
358 if let Some(entry) = self.entries.get(eval_key) {
359 let depends_on_subform_item =
360 entry.dep_versions.keys().any(|p| self.is_subform_dep(p));
361 if depends_on_subform_item && entry.computed_for_item != Some(idx) {
362 return None;
363 }
364
365 let has_changed_item_dep = self.subform_caches.get(&idx).is_some_and(|cache| {
366 entry.dep_versions.keys().any(|p| {
367 if p.starts_with("/$params") {
368 false
369 } else {
370 cache.data_versions.get(p) > self.data_versions.get(p)
371 }
372 })
373 });
374 let index_safe = match entry.computed_for_item {
375 None => !has_changed_item_dep,
376 Some(stored_idx) if stored_idx == idx => true,
377 _ => !has_changed_item_dep,
378 };
379 if index_safe {
380 let result =
381 self.validate_entry(eval_key, deps, &self.entries, item_data_versions);
382 if result.is_some() {
383 if crate::utils::is_debug_cache_enabled() {
384 println!(
385 "Cache HIT [T2 idx={} for={:?}] {}",
386 idx, entry.computed_for_item, eval_key
387 );
388 }
389 }
390 return result;
391 }
392 }
393
394 None
395 } else {
396 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
397 }
398 }
399
400 pub fn check_table_cache(
404 &self,
405 eval_key: &str,
406 deps: &IndexSet<String>,
407 ) -> Option<std::sync::Arc<Value>> {
408 if let Some(idx) = self.active_item_index {
409 if let Some(cache) = self.subform_caches.get(&idx) {
411 if let Some(hit) =
412 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
413 {
414 if crate::utils::is_debug_cache_enabled() {
415 println!("Cache HIT [T1 table idx={}] {}", idx, eval_key);
416 }
417 return Some(hit);
418 }
419 }
420
421 let has_changed_item_dep = self.subform_caches.get(&idx).is_some_and(|cache| {
424 if let Some(entry) = self.entries.get(eval_key) {
425 entry.dep_versions.keys().any(|p| {
426 if p.starts_with("/$params") {
427 false
428 } else {
429 cache.data_versions.get(p) > self.data_versions.get(p)
430 }
431 })
432 } else {
433 deps.iter().any(|dep| {
434 let p = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
435 if p.starts_with("/$params") {
436 false
437 } else {
438 cache.data_versions.get(&p) > self.data_versions.get(&p)
439 }
440 })
441 }
442 });
443 if has_changed_item_dep {
444 return None;
445 }
446
447 let result = self.validate_entry(eval_key, deps, &self.entries, &self.data_versions);
448 if result.is_some() {
449 if crate::utils::is_debug_cache_enabled() {
450 println!("Cache HIT [T2 table idx={}] {}", idx, eval_key);
451 }
452 }
453 result
454 } else {
455 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
456 }
457 }
458
459 fn validate_entry(
460 &self,
461 eval_key: &str,
462 _deps: &IndexSet<String>,
463 entries: &HashMap<String, CacheEntry>,
464 data_versions: &VersionTracker,
465 ) -> Option<std::sync::Arc<Value>> {
466 let entry = entries.get(eval_key)?;
467 for (data_dep_path, &cached_ver) in &entry.dep_versions {
468 let current_ver = if data_dep_path.starts_with("/$params") {
469 self.params_versions.get(data_dep_path)
470 } else if let Some(idx) = self.active_item_index {
471 self.subform_caches
472 .get(&idx)
473 .map(|c| c.data_versions.get(data_dep_path))
474 .filter(|&v| v > 0)
475 .unwrap_or_else(|| data_versions.get(data_dep_path))
476 } else {
477 data_versions.get(data_dep_path)
478 };
479
480 if current_ver != cached_ver {
481 if crate::utils::is_debug_cache_enabled() {
482 println!(
483 "Cache MISS {}: dep {} changed ({} -> {})",
484 eval_key, data_dep_path, cached_ver, current_ver
485 );
486 }
487 return None;
488 }
489 }
490 if crate::utils::is_debug_cache_enabled() {
491 println!("Cache HIT {}", eval_key);
492 }
493 Some(std::sync::Arc::clone(&entry.result))
494 }
495
496 pub fn store_cache(&mut self, eval_key: &str, deps: &IndexSet<String>, result: Value) {
500 self.store_cache_arc(eval_key, deps, std::sync::Arc::new(result));
501 }
502
503 pub fn store_cache_arc(
505 &mut self,
506 eval_key: &str,
507 deps: &IndexSet<String>,
508 result: std::sync::Arc<Value>,
509 ) {
510 let mut dep_versions = HashMap::with_capacity(deps.len());
512 {
513 let data_versions = if let Some(idx) = self.active_item_index {
514 self.ensure_active_item_cache(idx);
515 &self.subform_caches[&idx].data_versions
516 } else {
517 &self.data_versions
518 };
519
520 for dep in deps {
521 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
522 let ver = if data_dep_path.starts_with("/$params") {
523 self.params_versions.get(&data_dep_path)
524 } else {
525 data_versions.get(&data_dep_path)
526 };
527 dep_versions.insert(data_dep_path.into_owned(), ver);
528 }
529 }
530
531 let computed_for_item = self.active_item_index;
533
534 if eval_key.starts_with("#/$params") {
536 let existing_result: Option<&Value> = if let Some(idx) = self.active_item_index {
537 self.entries
539 .get(eval_key)
540 .map(|e| e.result.as_ref())
541 .or_else(|| {
542 self.subform_caches
543 .get(&idx)
544 .and_then(|c| c.entries.get(eval_key))
545 .map(|e| e.result.as_ref())
546 })
547 } else {
548 self.entries.get(eval_key).map(|e| e.result.as_ref())
549 };
550
551 let value_changed = existing_result.map_or(true, |r| r != result.as_ref());
552
553 if value_changed {
554 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(eval_key);
555
556 let mut current_path = data_path.as_ref();
559 let mut slash_count = current_path.matches('/').count();
560
561 while slash_count >= 3 {
562 self.params_versions.bump(current_path, "store_cache");
563 if let Some(last_slash) = current_path.rfind('/') {
564 current_path = ¤t_path[..last_slash];
565 slash_count -= 1;
566 } else {
567 break;
568 }
569 }
570
571 self.eval_generation += 1;
572 }
573 }
574
575 let entry = CacheEntry {
576 dep_versions,
577 result: std::sync::Arc::clone(&result),
578 computed_for_item,
579 };
580
581 if let Some(idx) = self.active_item_index {
582 self.subform_caches
584 .get_mut(&idx)
585 .unwrap()
586 .entries
587 .insert(eval_key.to_string(), entry.clone());
588
589 if eval_key.starts_with("#/$params") {
591 let t2_dep_versions: HashMap<String, u64> = entry
592 .dep_versions
593 .iter()
594 .map(|(path, &item_ver)| {
595 let parent_ver = if path.starts_with("/$params") {
596 item_ver } else {
598 self.data_versions.get(path)
600 };
601 (path.clone(), parent_ver)
602 })
603 .collect();
604
605 let t2_entry = CacheEntry {
606 dep_versions: t2_dep_versions,
607 result: std::sync::Arc::clone(&entry.result),
608 computed_for_item,
609 };
610 self.entries.insert(eval_key.to_string(), t2_entry);
611 }
612 } else {
613 self.entries.insert(eval_key.to_string(), entry);
614 }
615 }
616}
617
618pub(crate) fn diff_and_update_versions(
620 tracker: &mut VersionTracker,
621 pointer: &str,
622 old: &Value,
623 new: &Value,
624 source: &str,
625) {
626 let mut pointer_buf = String::with_capacity(128);
627 pointer_buf.push_str(pointer);
628 diff_and_update_versions_internal(tracker, &mut pointer_buf, old, new, source);
629}
630
631#[cfg(test)]
632mod cache_tests {
633 use super::{CacheEntry, EvalCache};
634 use indexmap::IndexSet;
635 use serde_json::json;
636 use std::collections::HashMap;
637 use std::sync::Arc;
638
639 #[test]
640 fn unchanged_active_item_reuses_global_table_with_item_dependency() {
641 let mut cache = EvalCache::new();
642 cache.set_active_item(1);
643
644 let eval_key = "#/$params/references/LOOKUP_RATE";
645 let deps = IndexSet::from_iter(["#/items/properties/benefit".to_string()]);
646 cache.entries.insert(
647 eval_key.to_string(),
648 CacheEntry {
649 dep_versions: HashMap::from([("/items/benefit".to_string(), 0)]),
650 result: Arc::new(json!([{"rate": 97}])),
651 computed_for_item: None,
652 },
653 );
654
655 assert_eq!(
656 cache.check_table_cache(eval_key, &deps),
657 Some(Arc::new(json!([{"rate": 97}]))),
658 "a scoped alias may reuse the parent result for its unchanged canonical item"
659 );
660 }
661
662 #[test]
663 fn changed_active_item_does_not_reuse_global_table_with_item_dependency() {
664 let mut cache = EvalCache::new();
665 cache.set_active_item(1);
666 cache
667 .subform_caches
668 .get_mut(&1)
669 .expect("active item cache must exist")
670 .data_versions
671 .bump("/items/benefit", "test item input change");
672
673 let eval_key = "#/$params/references/LOOKUP_RATE";
674 let deps = IndexSet::from_iter(["#/items/properties/benefit".to_string()]);
675 cache.entries.insert(
676 eval_key.to_string(),
677 CacheEntry {
678 dep_versions: HashMap::from([("/items/benefit".to_string(), 0)]),
679 result: Arc::new(json!([{"rate": 97}])),
680 computed_for_item: None,
681 },
682 );
683
684 assert!(
685 cache.check_table_cache(eval_key, &deps).is_none(),
686 "a changed item input must force item-scoped table recomputation"
687 );
688 }
689
690 #[test]
691 fn active_item_reuses_global_table_with_only_params_dependencies() {
692 let mut cache = EvalCache::new();
693 cache.set_active_item(1);
694
695 let eval_key = "#/$params/references/SHARED_RATE";
696 let deps = IndexSet::from_iter(["#/$params/others/currency".to_string()]);
697 cache.entries.insert(
698 eval_key.to_string(),
699 CacheEntry {
700 dep_versions: HashMap::from([("/$params/others/currency".to_string(), 0)]),
701 result: Arc::new(json!([{"rate": 10}])),
702 computed_for_item: None,
703 },
704 );
705
706 assert_eq!(
707 cache.check_table_cache(eval_key, &deps),
708 Some(Arc::new(json!([{"rate": 10}])))
709 );
710 }
711}
712
713fn diff_and_update_versions_internal(
714 tracker: &mut VersionTracker,
715 pointer: &mut String,
716 old: &Value,
717 new: &Value,
718 source: &str,
719) {
720 if old == new {
721 return;
722 }
723
724 if crate::utils::is_debug_cache_enabled() {
725 println!(
726 "[diff_and_update_versions_internal] {} pointer={}, old={:?}, new={:?}",
727 source, pointer, old, new
728 );
729 }
730
731 match (old, new) {
732 (Value::Object(a), Value::Object(b)) => {
733 for (key, a_val) in a {
734 if key == "$params" {
735 continue;
736 }
737 let b_val = b.get(key).unwrap_or(&Value::Null);
738 if a_val == b_val {
739 continue;
740 }
741
742 let old_len = pointer.len();
743 pointer.push('/');
744 if key.contains('~') || key.contains('/') {
745 let escaped_key = key.replace('~', "~0").replace('/', "~1");
746 pointer.push_str(&escaped_key);
747 } else {
748 pointer.push_str(key);
749 }
750 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
751 pointer.truncate(old_len);
752 }
753
754 for (key, b_val) in b {
755 if key == "$params" || a.contains_key(key) {
756 continue;
757 }
758 if b_val.is_null() {
759 continue;
760 }
761
762 let old_len = pointer.len();
763 pointer.push('/');
764 if key.contains('~') || key.contains('/') {
765 let escaped_key = key.replace('~', "~0").replace('/', "~1");
766 pointer.push_str(&escaped_key);
767 } else {
768 pointer.push_str(key);
769 }
770 diff_and_update_versions_internal(tracker, pointer, &Value::Null, b_val, source);
771 pointer.truncate(old_len);
772 }
773 }
774 (Value::Array(a), Value::Array(b)) => {
775 if a != b {
776 tracker.bump(pointer, source);
777 }
778 let max_len = a.len().max(b.len());
779 for i in 0..max_len {
780 let a_val = a.get(i).unwrap_or(&Value::Null);
781 let b_val = b.get(i).unwrap_or(&Value::Null);
782 if a_val == b_val {
783 continue;
784 }
785 let old_len = pointer.len();
786 use std::fmt::Write;
787 write!(pointer, "/{}", i).unwrap();
788 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
789 pointer.truncate(old_len);
790 }
791 }
792 (old_val, new_val) => {
793 if old_val != new_val {
794 if crate::utils::is_debug_cache_enabled() {
795 println!(
796 "[store_cache] Catch-all for {}: old={}, new={}",
797 pointer,
798 match old_val {
799 Value::Null => "Null",
800 Value::Bool(_) => "Bool",
801 Value::Number(_) => "Number",
802 Value::String(_) => "String",
803 Value::Array(_) => "Array",
804 Value::Object(_) => "Object",
805 },
806 match new_val {
807 Value::Null => "Null",
808 Value::Bool(_) => "Bool",
809 Value::Number(_) => "Number",
810 Value::String(_) => "String",
811 Value::Array(_) => "Array",
812 Value::Object(_) => "Object",
813 }
814 );
815 }
816 tracker.bump(pointer, "diff_and_update_versions_internal");
817
818 if old_val.is_object() || old_val.is_array() {
821 traverse_and_bump(tracker, pointer, old_val);
822 }
823 if new_val.is_object() || new_val.is_array() {
824 traverse_and_bump(tracker, pointer, new_val);
825 }
826 }
827 }
828 }
829}
830
831#[cfg(test)]
835mod tests {
836 use super::VersionTracker;
837
838 #[test]
839 fn merge_excluding_prefix_keeps_item_versions_isolated() {
840 let mut item = VersionTracker::new();
841 item.bump("/items/flag", "test");
842
843 let mut parent = VersionTracker::new();
844 parent.bump("/users/profile/name", "test");
845 parent.bump("/items/flag", "test");
846
847 item.merge_excluding_prefix(&parent, "/items/");
848
849 assert_eq!(item.get("/users/profile/name"), 1);
850 assert_eq!(
851 item.get("/items/flag"),
852 1,
853 "another item's parent-tracker bump must not alter this item's version"
854 );
855 }
856}
857
858fn traverse_and_bump(tracker: &mut VersionTracker, pointer: &mut String, val: &Value) {
859 match val {
860 Value::Object(map) => {
861 for (key, v) in map {
862 if key == "$params" {
863 continue; }
865 let escaped_key = key.replace('~', "~0").replace('/', "~1");
866 let old_len = pointer.len();
867 pointer.push('/');
868 pointer.push_str(&escaped_key);
869 tracker.bump(pointer, "traverse_and_bump1");
870 traverse_and_bump(tracker, pointer, v);
871 pointer.truncate(old_len);
872 }
873 }
874 Value::Array(arr) => {
875 for (i, v) in arr.iter().enumerate() {
876 let old_len = pointer.len();
877 use std::fmt::Write;
878 write!(pointer, "/{}", i).unwrap();
879 tracker.bump(pointer, "traverse_and_bump2");
880 traverse_and_bump(tracker, pointer, v);
881 pointer.truncate(old_len);
882 }
883 }
884 _ => {}
885 }
886}