toon_format/utils/
mod.rs

1pub mod literal;
2pub mod number;
3pub mod string;
4pub mod validation;
5
6use indexmap::IndexMap;
7pub use literal::{
8    is_keyword,
9    is_literal_like,
10    is_numeric_like,
11    is_structural_char,
12};
13pub use number::format_canonical_number;
14pub use string::{
15    escape_string,
16    is_valid_unquoted_key,
17    needs_quoting,
18    quote_string,
19    unescape_string,
20};
21
22use crate::types::{
23    JsonValue as Value,
24    Number,
25};
26
27/// Context for determining when quoting is needed.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum QuotingContext {
30    ObjectValue,
31    ArrayValue,
32}
33
34/// Normalize a JSON value (converts NaN/Infinity to null, -0 to 0).
35pub fn normalize(value: Value) -> Value {
36    match value {
37        Value::Number(n) => {
38            // Handle NegInt(0) case - convert to PosInt(0)
39            if let Number::NegInt(0) = n {
40                Value::Number(Number::from(0u64))
41            } else if let Some(f) = n.as_f64() {
42                if f.is_nan() || f.is_infinite() {
43                    Value::Null
44                } else if f == 0.0 && f.is_sign_negative() {
45                    Value::Number(Number::from(0u64))
46                } else {
47                    Value::Number(n)
48                }
49            } else {
50                Value::Number(n)
51            }
52        }
53        Value::Object(obj) => {
54            let normalized: IndexMap<String, Value> =
55                obj.into_iter().map(|(k, v)| (k, normalize(v))).collect();
56            Value::Object(normalized)
57        }
58        Value::Array(arr) => {
59            let normalized: Vec<Value> = arr.into_iter().map(normalize).collect();
60            Value::Array(normalized)
61        }
62        _ => value,
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use core::f64;
69
70    use serde_json::json;
71
72    use super::*;
73
74    #[test]
75    fn test_normalize_nan() {
76        let value = Value::from(json!(f64::NAN));
77        let normalized = normalize(value);
78        assert_eq!(normalized, Value::from(json!(null)));
79    }
80
81    #[test]
82    fn test_normalize_infinity() {
83        let value = Value::from(json!(f64::INFINITY));
84        let normalized = normalize(value);
85        assert_eq!(normalized, Value::from(json!(null)));
86
87        let value = Value::from(json!(f64::NEG_INFINITY));
88        let normalized = normalize(value);
89        assert_eq!(normalized, Value::from(json!(null)));
90    }
91
92    #[test]
93    fn test_normalize_negative_zero() {
94        let value = Value::from(json!(-0.0));
95        let normalized = normalize(value);
96        assert_eq!(normalized, Value::from(json!(0)));
97    }
98
99    #[test]
100    fn test_normalize_nested() {
101        let value = Value::from(json!({
102            "a": f64::NAN,
103            "b": {
104                "c": f64::INFINITY
105            },
106            "d": [1, f64::NAN, 3]
107        }));
108
109        let normalized = normalize(value);
110        assert_eq!(
111            normalized,
112            Value::from(json!({
113                "a": null,
114                "b": {
115                    "c": null
116                },
117                "d": [1, null, 3]
118            }))
119        );
120    }
121
122    #[test]
123    fn test_normalize_normal_values() {
124        let value = Value::from(json!({
125            "name": "Alice",
126            "age": 30,
127            "score": f64::consts::PI
128        }));
129
130        let normalized = normalize(value.clone());
131        assert_eq!(normalized, value);
132    }
133}