json_eval_rs/utils/
mod.rs

1use serde_json::Value;
2use std::cell::RefCell;
3
4
5// Timing infrastructure
6thread_local! {
7    static TIMING_ENABLED: RefCell<bool> = RefCell::new(std::env::var("JSONEVAL_TIMING").is_ok());
8    static TIMING_DATA: RefCell<Vec<(String, std::time::Duration)>> = RefCell::new(Vec::new());
9}
10
11/// Check if timing is enabled
12#[inline]
13pub fn is_timing_enabled() -> bool {
14    TIMING_ENABLED.with(|enabled| *enabled.borrow())
15}
16
17/// Enable timing programmatically (in addition to JSONEVAL_TIMING environment variable)
18pub fn enable_timing() {
19    TIMING_ENABLED.with(|enabled| {
20        *enabled.borrow_mut() = true;
21    });
22}
23
24/// Disable timing
25pub fn disable_timing() {
26    TIMING_ENABLED.with(|enabled| {
27        *enabled.borrow_mut() = false;
28    });
29}
30
31/// Record timing data
32#[inline]
33pub fn record_timing(label: &str, duration: std::time::Duration) {
34    if is_timing_enabled() {
35        TIMING_DATA.with(|data| {
36            data.borrow_mut().push((label.to_string(), duration));
37        });
38    }
39}
40
41/// Print timing summary
42pub fn print_timing_summary() {
43    if !is_timing_enabled() {
44        return;
45    }
46
47    TIMING_DATA.with(|data| {
48        let timings = data.borrow();
49        if timings.is_empty() {
50            return;
51        }
52
53        eprintln!("\nšŸ“Š Timing Summary (JSONEVAL_TIMING enabled)");
54        eprintln!("{}", "=".repeat(60));
55
56        let mut total = std::time::Duration::ZERO;
57        for (label, duration) in timings.iter() {
58            eprintln!("{:40} {:>12?}", label, duration);
59            total += *duration;
60        }
61
62        eprintln!("{}", "=".repeat(60));
63        eprintln!("{:40} {:>12?}", "TOTAL", total);
64        eprintln!();
65    });
66}
67
68/// Clear timing data
69pub fn clear_timing_data() {
70    TIMING_DATA.with(|data| {
71        data.borrow_mut().clear();
72    });
73}
74
75/// Macro for timing a block of code
76#[macro_export]
77macro_rules! time_block {
78    ($label:expr, $block:block) => {{
79        let _start = if $crate::utils::is_timing_enabled() {
80            Some(std::time::Instant::now())
81        } else {
82            None
83        };
84        let result = $block;
85        if let Some(start) = _start {
86            $crate::utils::record_timing($label, start.elapsed());
87        }
88        result
89    }};
90}
91
92/// Clean floating point noise from JSON values
93/// Converts values very close to zero (< 1e-10) to exactly 0
94pub fn clean_float_noise(value: Value) -> Value {
95    const EPSILON: f64 = 1e-10;
96
97    match value {
98        Value::Number(n) => {
99            if let Some(f) = n.as_f64() {
100                if f.abs() < EPSILON {
101                    // Clean near-zero values to exactly 0
102                    Value::Number(serde_json::Number::from(0))
103                } else if f.fract().abs() < EPSILON {
104                    // Clean whole numbers: 33.0 → 33
105                    Value::Number(serde_json::Number::from(f.round() as i64))
106                } else {
107                    Value::Number(n)
108                }
109            } else {
110                Value::Number(n)
111            }
112        }
113        Value::Array(arr) => Value::Array(arr.into_iter().map(clean_float_noise).collect()),
114        Value::Object(obj) => Value::Object(
115            obj.into_iter()
116                .map(|(k, v)| (k, clean_float_noise(v)))
117                .collect(),
118        ),
119        _ => value,
120    }
121}