nodedb_types/value/
coerce.rs1use super::core::Value;
8
9impl Value {
10 pub fn eq_coerced(&self, other: &Value) -> bool {
15 match (self, other) {
16 (Value::Null, Value::Null) => true,
17 (Value::Bool(a), Value::Bool(b)) => a == b,
18 (Value::Integer(a), Value::Integer(b)) => a == b,
19 (Value::Integer(a), Value::Float(b)) => *a as f64 == *b,
20 (Value::Float(a), Value::Integer(b)) => *a == *b as f64,
21 (Value::Float(a), Value::Float(b)) => a == b,
22 (Value::String(a), Value::String(b)) => a == b,
23 (Value::Integer(a), Value::String(s)) => {
25 s.parse::<i64>().is_ok_and(|n| *a == n)
26 || s.parse::<f64>().is_ok_and(|n| *a as f64 == n)
27 }
28 (Value::String(s), Value::Integer(b)) => {
29 s.parse::<i64>().is_ok_and(|n| n == *b)
30 || s.parse::<f64>().is_ok_and(|n| n == *b as f64)
31 }
32 (Value::Float(a), Value::String(s)) => s.parse::<f64>().is_ok_and(|n| *a == n),
33 (Value::String(s), Value::Float(b)) => s.parse::<f64>().is_ok_and(|n| n == *b),
34 (Value::ArrayCell(a), Value::ArrayCell(b)) => a == b,
36 _ => false,
37 }
38 }
39
40 pub fn cmp_coerced(&self, other: &Value) -> std::cmp::Ordering {
44 use std::cmp::Ordering;
45 if let (Value::ArrayCell(a), Value::ArrayCell(b)) = (self, other) {
48 for (x, y) in a.coords.iter().zip(b.coords.iter()) {
49 match x.cmp_coerced(y) {
50 Ordering::Equal => continue,
51 non_eq => return non_eq,
52 }
53 }
54 match a.coords.len().cmp(&b.coords.len()) {
55 Ordering::Equal => {}
56 non_eq => return non_eq,
57 }
58 for (x, y) in a.attrs.iter().zip(b.attrs.iter()) {
59 match x.cmp_coerced(y) {
60 Ordering::Equal => continue,
61 non_eq => return non_eq,
62 }
63 }
64 return a.attrs.len().cmp(&b.attrs.len());
65 }
66 let self_f64 = match self {
67 Value::Integer(i) => Some(*i as f64),
68 Value::Float(f) => Some(*f),
69 Value::String(s) => s.parse::<f64>().ok(),
70 _ => None,
71 };
72 let other_f64 = match other {
73 Value::Integer(i) => Some(*i as f64),
74 Value::Float(f) => Some(*f),
75 Value::String(s) => s.parse::<f64>().ok(),
76 _ => None,
77 };
78 if let (Some(a), Some(b)) = (self_f64, other_f64) {
79 return a.partial_cmp(&b).unwrap_or(Ordering::Equal);
80 }
81 let a_str = match self {
82 Value::String(s) => s.as_str(),
83 _ => return Ordering::Equal,
84 };
85 let b_str = match other {
86 Value::String(s) => s.as_str(),
87 _ => return Ordering::Equal,
88 };
89 a_str.cmp(b_str)
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn eq_coerced_same_type() {
99 assert!(Value::Null.eq_coerced(&Value::Null));
100 assert!(Value::Bool(true).eq_coerced(&Value::Bool(true)));
101 assert!(!Value::Bool(true).eq_coerced(&Value::Bool(false)));
102 assert!(Value::Integer(42).eq_coerced(&Value::Integer(42)));
103 assert!(Value::Float(2.78).eq_coerced(&Value::Float(2.78)));
104 assert!(Value::String("hello".into()).eq_coerced(&Value::String("hello".into())));
105 }
106
107 #[test]
108 fn eq_coerced_int_float() {
109 assert!(Value::Integer(5).eq_coerced(&Value::Float(5.0)));
110 assert!(Value::Float(5.0).eq_coerced(&Value::Integer(5)));
111 assert!(!Value::Integer(5).eq_coerced(&Value::Float(5.1)));
112 }
113
114 #[test]
115 fn eq_coerced_string_number() {
116 assert!(Value::String("5".into()).eq_coerced(&Value::Integer(5)));
117 assert!(Value::Integer(5).eq_coerced(&Value::String("5".into())));
118 assert!(Value::String("2.78".into()).eq_coerced(&Value::Float(2.78)));
119 assert!(Value::Float(2.78).eq_coerced(&Value::String("2.78".into())));
120 assert!(!Value::String("abc".into()).eq_coerced(&Value::Integer(5)));
121 assert!(!Value::Integer(5).eq_coerced(&Value::String("abc".into())));
122 }
123
124 #[test]
125 fn eq_coerced_cross_type_false() {
126 assert!(!Value::Bool(true).eq_coerced(&Value::Integer(1)));
127 assert!(!Value::Null.eq_coerced(&Value::Integer(0)));
128 assert!(!Value::Null.eq_coerced(&Value::String("".into())));
129 }
130
131 #[test]
132 fn cmp_coerced_numeric() {
133 use std::cmp::Ordering;
134 assert_eq!(
135 Value::Integer(5).cmp_coerced(&Value::Integer(10)),
136 Ordering::Less
137 );
138 assert_eq!(
139 Value::Integer(10).cmp_coerced(&Value::Float(5.0)),
140 Ordering::Greater
141 );
142 assert_eq!(
143 Value::String("90".into()).cmp_coerced(&Value::Integer(80)),
144 Ordering::Greater
145 );
146 assert_eq!(
147 Value::Float(2.78).cmp_coerced(&Value::String("2.78".into())),
148 Ordering::Equal
149 );
150 }
151
152 #[test]
153 fn cmp_coerced_string_fallback() {
154 use std::cmp::Ordering;
155 assert_eq!(
156 Value::String("abc".into()).cmp_coerced(&Value::String("def".into())),
157 Ordering::Less
158 );
159 assert_eq!(
160 Value::String("z".into()).cmp_coerced(&Value::String("a".into())),
161 Ordering::Greater
162 );
163 }
164
165 #[test]
166 fn eq_coerced_symmetry() {
167 let cases = [
168 (Value::Integer(42), Value::String("42".into())),
169 (Value::Float(2.78), Value::String("2.78".into())),
170 (Value::Integer(5), Value::Float(5.0)),
171 ];
172 for (a, b) in &cases {
173 assert_eq!(
174 a.eq_coerced(b),
175 b.eq_coerced(a),
176 "symmetry violated for {a:?} vs {b:?}"
177 );
178 }
179 }
180}