nodedb_query/
value_ops.rs1use std::cmp::Ordering;
10
11use nodedb_types::Value;
12
13pub fn value_to_f64(v: &Value, coerce_bool: bool) -> Option<f64> {
20 match v {
21 Value::Integer(i) => Some(*i as f64),
22 Value::Float(f) => Some(*f),
23 Value::String(s) => s.parse::<f64>().ok(),
24 Value::Bool(b) if coerce_bool => Some(if *b { 1.0 } else { 0.0 }),
25 Value::Decimal(d) => {
26 use rust_decimal::prelude::ToPrimitive;
27 d.to_f64()
28 }
29 _ => None,
30 }
31}
32
33pub fn compare_values(a: &Value, b: &Value) -> Ordering {
38 if let (Some(na), Some(nb)) = (value_to_f64(a, true), value_to_f64(b, true)) {
39 return na.partial_cmp(&nb).unwrap_or(Ordering::Equal);
40 }
41 let sa = value_to_display_string(a);
42 let sb = value_to_display_string(b);
43 sa.cmp(&sb)
44}
45
46pub fn coerced_eq(a: &Value, b: &Value) -> bool {
51 if a == b {
52 return true;
53 }
54 if let (Some(af), Some(bf)) = (value_to_f64(a, true), value_to_f64(b, true)) {
55 return (af - bf).abs() < f64::EPSILON;
56 }
57 false
58}
59
60pub fn is_truthy(v: &Value) -> bool {
68 match v {
69 Value::Bool(b) => *b,
70 Value::Null => false,
71 Value::Integer(i) => *i != 0,
72 Value::Float(f) => *f != 0.0,
73 Value::String(s) => !s.is_empty(),
74 _ => true,
75 }
76}
77
78pub fn value_to_display_string(v: &Value) -> String {
85 match v {
86 Value::String(s) => s.clone(),
87 Value::Null => String::new(),
88 Value::Integer(i) => i.to_string(),
89 Value::Float(f) => f.to_string(),
90 Value::Bool(b) => b.to_string(),
91 Value::Uuid(s) | Value::Ulid(s) | Value::Regex(s) => s.clone(),
92 Value::DateTime(dt) | Value::NaiveDateTime(dt) => dt.to_iso8601(),
93 Value::Duration(d) => d.to_human(),
94 Value::Decimal(d) => d.to_string(),
95 other => {
96 let json = serde_json::Value::from(other.clone());
98 json.to_string()
99 }
100 }
101}
102
103pub fn to_value_number(n: f64) -> Value {
107 if n.is_nan() || n.is_infinite() {
108 Value::Null
109 } else if n.fract() == 0.0 && n.abs() < i64::MAX as f64 {
110 Value::Integer(n as i64)
111 } else {
112 Value::Float(n)
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn coerced_eq_mixed_types() {
122 assert!(coerced_eq(&Value::Integer(5), &Value::String("5".into())));
123 assert!(!coerced_eq(&Value::Integer(5), &Value::String("6".into())));
124 }
125
126 #[test]
127 fn coerced_eq_bool_numeric() {
128 assert!(coerced_eq(&Value::Bool(true), &Value::Integer(1)));
129 assert!(coerced_eq(&Value::Bool(false), &Value::Integer(0)));
130 assert!(!coerced_eq(&Value::Bool(true), &Value::Integer(0)));
131 }
132
133 #[test]
134 fn compare_numeric_coercion() {
135 assert_eq!(
136 compare_values(&Value::Integer(5), &Value::String("4".into())),
137 Ordering::Greater
138 );
139 }
140
141 #[test]
142 fn truthiness() {
143 assert!(is_truthy(&Value::Bool(true)));
144 assert!(!is_truthy(&Value::Bool(false)));
145 assert!(!is_truthy(&Value::Null));
146 assert!(is_truthy(&Value::Integer(1)));
147 assert!(!is_truthy(&Value::Integer(0)));
148 assert!(is_truthy(&Value::String("hello".into())));
149 assert!(!is_truthy(&Value::String(String::new())));
150 }
151
152 #[test]
153 fn to_value_number_nan() {
154 assert_eq!(to_value_number(f64::NAN), Value::Null);
155 }
156
157 #[test]
158 fn to_value_number_integer() {
159 assert_eq!(to_value_number(42.0), Value::Integer(42));
160 }
161
162 #[test]
163 fn to_value_number_float() {
164 assert_eq!(to_value_number(3.15), Value::Float(3.15));
165 }
166}