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 = arr.len() as i32;
128 if len == 0 {
129 return Some(vec![]);
130 }
131
132 let a: i32 = match start {
133 Some(s) => adjust_slice_endpoint(len, s, step),
134 _ if step < 0 => len - 1,
135 _ => 0,
136 };
137 let b: i32 = match stop {
138 Some(s) => adjust_slice_endpoint(len, s, step),
139 _ if step < 0 => -1,
140 _ => len,
141 };
142
143 let mut result = Vec::new();
144 let mut i = a;
145 if step > 0 {
146 while i < b {
147 result.push(arr[i as usize].clone());
148 i += step;
149 }
150 } else {
151 while i > b {
152 result.push(arr[i as usize].clone());
153 i += step;
154 }
155 }
156 Some(result)
157 }
158
159 fn compare(&self, comparator: &Comparator, other: &Value) -> Option<bool> {
160 match comparator {
161 Comparator::Equal => Some(values_equal(self, other)),
162 Comparator::NotEqual => Some(!values_equal(self, other)),
163 Comparator::LessThan => compare_ordered(self, other).map(|o| o.is_lt()),
164 Comparator::LessThanEqual => compare_ordered(self, other).map(|o| o.is_le()),
165 Comparator::GreaterThan => compare_ordered(self, other).map(|o| o.is_gt()),
166 Comparator::GreaterThanEqual => compare_ordered(self, other).map(|o| o.is_ge()),
167 }
168 }
169
170 fn is_expref(&self) -> bool {
171 matches!(self, Value::Object(map) if map.contains_key("__jpx_expref__"))
172 }
173}
174
175fn adjust_slice_endpoint(len: i32, mut endpoint: i32, step: i32) -> i32 {
177 if endpoint < 0 {
178 endpoint += len;
179 if endpoint >= 0 {
180 endpoint
181 } else if step < 0 {
182 -1
183 } else {
184 0
185 }
186 } else if endpoint < len {
187 endpoint
188 } else if step < 0 {
189 len - 1
190 } else {
191 len
192 }
193}
194
195fn values_equal(a: &Value, b: &Value) -> bool {
197 match (a, b) {
198 (Value::Null, Value::Null) => true,
199 (Value::Bool(a), Value::Bool(b)) => a == b,
200 (Value::Number(a), Value::Number(b)) => {
201 a.as_f64().zip(b.as_f64()).is_some_and(|(af, bf)| af == bf)
203 }
204 (Value::String(a), Value::String(b)) => a == b,
205 (Value::Array(a), Value::Array(b)) => {
206 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y))
207 }
208 (Value::Object(a), Value::Object(b)) => {
209 a.len() == b.len()
210 && a.iter()
211 .all(|(k, v)| b.get(k).is_some_and(|bv| values_equal(v, bv)))
212 }
213 _ => false,
214 }
215}
216
217fn compare_ordered(a: &Value, b: &Value) -> Option<std::cmp::Ordering> {
219 match (a, b) {
220 (Value::Number(a), Value::Number(b)) => {
221 let af = a.as_f64()?;
222 let bf = b.as_f64()?;
223 af.partial_cmp(&bf)
224 }
225 (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
226 _ => None,
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use serde_json::json;
234
235 #[test]
236 fn test_truthy() {
237 assert!(!Value::Null.is_truthy());
238 assert!(!Value::Bool(false).is_truthy());
239 assert!(Value::Bool(true).is_truthy());
240 assert!(!Value::String("".into()).is_truthy());
241 assert!(Value::String("hi".into()).is_truthy());
242 assert!(!json!([]).is_truthy());
243 assert!(json!([1]).is_truthy());
244 assert!(!json!({}).is_truthy());
245 assert!(json!({"a": 1}).is_truthy());
246 assert!(json!(0).is_truthy());
247 assert!(json!(42).is_truthy());
248 }
249
250 #[test]
251 fn test_get_field() {
252 let obj = json!({"foo": "bar"});
253 assert_eq!(obj.get_field("foo"), json!("bar"));
254 assert_eq!(obj.get_field("missing"), Value::Null);
255 assert_eq!(Value::Null.get_field("x"), Value::Null);
256 }
257
258 #[test]
259 fn test_get_index() {
260 let arr = json!([10, 20, 30]);
261 assert_eq!(arr.get_index(0), json!(10));
262 assert_eq!(arr.get_index(2), json!(30));
263 assert_eq!(arr.get_index(5), Value::Null);
264 }
265
266 #[test]
267 fn test_get_negative_index() {
268 let arr = json!([10, 20, 30]);
269 assert_eq!(arr.get_negative_index(1), json!(30));
270 assert_eq!(arr.get_negative_index(3), json!(10));
271 assert_eq!(arr.get_negative_index(4), Value::Null);
272 }
273
274 #[test]
275 fn test_slice() {
276 let arr = json!([0, 1, 2, 3, 4, 5]);
277 assert_eq!(
278 arr.slice(Some(1), Some(4), 1),
279 Some(vec![json!(1), json!(2), json!(3)])
280 );
281 assert_eq!(
282 arr.slice(None, None, 2),
283 Some(vec![json!(0), json!(2), json!(4)])
284 );
285 assert_eq!(
286 arr.slice(None, None, -1),
287 Some(vec![
288 json!(5),
289 json!(4),
290 json!(3),
291 json!(2),
292 json!(1),
293 json!(0)
294 ])
295 );
296 }
297
298 #[test]
299 fn test_compare() {
300 assert_eq!(json!(1).compare(&Comparator::Equal, &json!(1)), Some(true));
301 assert_eq!(
302 json!(1).compare(&Comparator::LessThan, &json!(2)),
303 Some(true)
304 );
305 assert_eq!(
306 json!("a").compare(&Comparator::GreaterThan, &json!("b")),
307 Some(false)
308 );
309 assert_eq!(json!(1).compare(&Comparator::LessThan, &json!("a")), None);
311 assert_eq!(
313 json!(1).compare(&Comparator::Equal, &json!("a")),
314 Some(false)
315 );
316 }
317
318 #[test]
319 fn test_jmespath_type() {
320 assert_eq!(Value::Null.jmespath_type(), JmespathType::Null);
321 assert_eq!(json!(true).jmespath_type(), JmespathType::Boolean);
322 assert_eq!(json!(42).jmespath_type(), JmespathType::Number);
323 assert_eq!(json!("s").jmespath_type(), JmespathType::String);
324 assert_eq!(json!([]).jmespath_type(), JmespathType::Array);
325 assert_eq!(json!({}).jmespath_type(), JmespathType::Object);
326 }
327}