1use serde_json::Value;
7
8use crate::ast::Comparator;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum JmespathType {
13 Null,
14 String,
15 Number,
16 Boolean,
17 Array,
18 Object,
19 Expref,
20}
21
22impl std::fmt::Display for JmespathType {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 JmespathType::Null => write!(f, "null"),
26 JmespathType::String => write!(f, "string"),
27 JmespathType::Number => write!(f, "number"),
28 JmespathType::Boolean => write!(f, "boolean"),
29 JmespathType::Array => write!(f, "array"),
30 JmespathType::Object => write!(f, "object"),
31 JmespathType::Expref => write!(f, "expref"),
32 }
33 }
34}
35
36pub trait ValueExt {
38 fn jmespath_type(&self) -> JmespathType;
40
41 fn is_truthy(&self) -> bool;
50
51 fn get_field(&self, name: &str) -> Value;
53
54 fn get_index(&self, idx: usize) -> Value;
56
57 fn get_negative_index(&self, idx: usize) -> Value;
59
60 fn slice(&self, start: Option<i32>, stop: Option<i32>, step: i32) -> Option<Vec<Value>>;
63
64 fn compare(&self, comparator: &Comparator, other: &Value) -> Option<bool>;
67
68 fn is_expref(&self) -> bool;
70}
71
72impl ValueExt for Value {
73 fn jmespath_type(&self) -> JmespathType {
74 if self.is_expref() {
75 return JmespathType::Expref;
76 }
77 match self {
78 Value::Null => JmespathType::Null,
79 Value::Bool(_) => JmespathType::Boolean,
80 Value::Number(_) => JmespathType::Number,
81 Value::String(_) => JmespathType::String,
82 Value::Array(_) => JmespathType::Array,
83 Value::Object(_) => JmespathType::Object,
84 }
85 }
86
87 fn is_truthy(&self) -> bool {
88 match self {
89 Value::Null => false,
90 Value::Bool(b) => *b,
91 Value::String(s) => !s.is_empty(),
92 Value::Array(a) => !a.is_empty(),
93 Value::Object(o) => !o.is_empty(),
94 Value::Number(_) => true,
95 }
96 }
97
98 fn get_field(&self, name: &str) -> Value {
99 match self {
100 Value::Object(map) => map.get(name).cloned().unwrap_or(Value::Null),
101 _ => Value::Null,
102 }
103 }
104
105 fn get_index(&self, idx: usize) -> Value {
106 match self {
107 Value::Array(arr) => arr.get(idx).cloned().unwrap_or(Value::Null),
108 _ => Value::Null,
109 }
110 }
111
112 fn get_negative_index(&self, idx: usize) -> Value {
113 match self {
114 Value::Array(arr) => {
115 if idx > arr.len() {
116 Value::Null
117 } else {
118 arr.get(arr.len() - idx).cloned().unwrap_or(Value::Null)
119 }
120 }
121 _ => Value::Null,
122 }
123 }
124
125 fn slice(&self, start: Option<i32>, stop: Option<i32>, step: i32) -> Option<Vec<Value>> {
126 let arr = self.as_array()?;
127 let len = i32::try_from(arr.len()).unwrap_or(i32::MAX);
132 if len == 0 {
133 return Some(vec![]);
134 }
135
136 let a: i32 = match start {
137 Some(s) => adjust_slice_endpoint(len, s, step),
138 _ if step < 0 => len - 1,
139 _ => 0,
140 };
141 let b: i32 = match stop {
142 Some(s) => adjust_slice_endpoint(len, s, step),
143 _ if step < 0 => -1,
144 _ => len,
145 };
146
147 let mut result = Vec::new();
148 let mut i = a;
149 if step > 0 {
150 while i < b {
151 result.push(arr[i as usize].clone());
152 i += step;
153 }
154 } else {
155 while i > b {
156 result.push(arr[i as usize].clone());
157 i += step;
158 }
159 }
160 Some(result)
161 }
162
163 fn compare(&self, comparator: &Comparator, other: &Value) -> Option<bool> {
164 match comparator {
165 Comparator::Equal => Some(values_equal(self, other)),
166 Comparator::NotEqual => Some(!values_equal(self, other)),
167 Comparator::LessThan => compare_ordered(self, other).map(|o| o.is_lt()),
168 Comparator::LessThanEqual => compare_ordered(self, other).map(|o| o.is_le()),
169 Comparator::GreaterThan => compare_ordered(self, other).map(|o| o.is_gt()),
170 Comparator::GreaterThanEqual => compare_ordered(self, other).map(|o| o.is_ge()),
171 }
172 }
173
174 fn is_expref(&self) -> bool {
175 matches!(self, Value::Object(map) if map.contains_key(crate::EXPREF_KEY.as_str()))
176 }
177}
178
179fn adjust_slice_endpoint(len: i32, mut endpoint: i32, step: i32) -> i32 {
181 if endpoint < 0 {
182 endpoint += len;
183 if endpoint >= 0 {
184 endpoint
185 } else if step < 0 {
186 -1
187 } else {
188 0
189 }
190 } else if endpoint < len {
191 endpoint
192 } else if step < 0 {
193 len - 1
194 } else {
195 len
196 }
197}
198
199fn values_equal(a: &Value, b: &Value) -> bool {
201 match (a, b) {
202 (Value::Null, Value::Null) => true,
203 (Value::Bool(a), Value::Bool(b)) => a == b,
204 (Value::Number(a), Value::Number(b)) => {
205 a.as_f64().zip(b.as_f64()).is_some_and(|(af, bf)| af == bf)
207 }
208 (Value::String(a), Value::String(b)) => a == b,
209 (Value::Array(a), Value::Array(b)) => {
210 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
211 }
212 (Value::Object(a), Value::Object(b)) => {
213 a.len() == b.len()
214 && a.iter()
215 .all(|(k, v)| b.get(k).is_some_and(|bv| values_equal(v, bv)))
216 }
217 _ => false,
218 }
219}
220
221fn compare_ordered(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
223 match (a, b) {
224 (Value::Number(a), Value::Number(b)) => {
225 let af = a.as_f64()?;
226 let bf = b.as_f64()?;
227 af.partial_cmp(&bf)
228 }
229 (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
230 _ => None,
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use serde_json::json;
238
239 #[test]
240 fn test_truthy() {
241 assert!(!Value::Null.is_truthy());
242 assert!(!Value::Bool(false).is_truthy());
243 assert!(Value::Bool(true).is_truthy());
244 assert!(!Value::String("".into()).is_truthy());
245 assert!(Value::String("hi".into()).is_truthy());
246 assert!(!json!([]).is_truthy());
247 assert!(json!([1]).is_truthy());
248 assert!(!json!({}).is_truthy());
249 assert!(json!({"a": 1}).is_truthy());
250 assert!(json!(0).is_truthy());
251 assert!(json!(42).is_truthy());
252 }
253
254 #[test]
255 fn test_get_field() {
256 let obj = json!({"foo": "bar"});
257 assert_eq!(obj.get_field("foo"), json!("bar"));
258 assert_eq!(obj.get_field("missing"), Value::Null);
259 assert_eq!(Value::Null.get_field("x"), Value::Null);
260 }
261
262 #[test]
263 fn test_get_index() {
264 let arr = json!([10, 20, 30]);
265 assert_eq!(arr.get_index(0), json!(10));
266 assert_eq!(arr.get_index(2), json!(30));
267 assert_eq!(arr.get_index(5), Value::Null);
268 }
269
270 #[test]
271 fn test_get_negative_index() {
272 let arr = json!([10, 20, 30]);
273 assert_eq!(arr.get_negative_index(1), json!(30));
274 assert_eq!(arr.get_negative_index(3), json!(10));
275 assert_eq!(arr.get_negative_index(4), Value::Null);
276 }
277
278 #[test]
279 fn test_slice() {
280 let arr = json!([0, 1, 2, 3, 4, 5]);
281 assert_eq!(
282 arr.slice(Some(1), Some(4), 1),
283 Some(vec![json!(1), json!(2), json!(3)])
284 );
285 assert_eq!(
286 arr.slice(None, None, 2),
287 Some(vec![json!(0), json!(2), json!(4)])
288 );
289 assert_eq!(
290 arr.slice(None, None, -1),
291 Some(vec![
292 json!(5),
293 json!(4),
294 json!(3),
295 json!(2),
296 json!(1),
297 json!(0)
298 ])
299 );
300 }
301
302 #[test]
303 fn test_compare() {
304 assert_eq!(json!(1).compare(&Comparator::Equal, &json!(1)), Some(true));
305 assert_eq!(
306 json!(1).compare(&Comparator::LessThan, &json!(2)),
307 Some(true)
308 );
309 assert_eq!(
310 json!("a").compare(&Comparator::GreaterThan, &json!("b")),
311 Some(false)
312 );
313 assert_eq!(json!(1).compare(&Comparator::LessThan, &json!("a")), None);
315 assert_eq!(
317 json!(1).compare(&Comparator::Equal, &json!("a")),
318 Some(false)
319 );
320 }
321
322 #[test]
323 fn test_jmespath_type() {
324 assert_eq!(Value::Null.jmespath_type(), JmespathType::Null);
325 assert_eq!(json!(true).jmespath_type(), JmespathType::Boolean);
326 assert_eq!(json!(42).jmespath_type(), JmespathType::Number);
327 assert_eq!(json!("s").jmespath_type(), JmespathType::String);
328 assert_eq!(json!([]).jmespath_type(), JmespathType::Array);
329 assert_eq!(json!({}).jmespath_type(), JmespathType::Object);
330 }
331}