1use serde_json::Value;
30use serde_json_path::JsonPath;
31
32use crate::error::NikaError;
33
34pub fn try_parse_json_str(value: &Value) -> Option<Value> {
42 if let Value::String(s) = value {
43 let trimmed = s.trim();
44 if (trimmed.starts_with('{') && trimmed.ends_with('}'))
45 || (trimmed.starts_with('[') && trimmed.ends_with(']'))
46 {
47 serde_json::from_str::<Value>(trimmed).ok()
48 } else {
49 None
50 }
51 } else {
52 None
53 }
54}
55
56pub fn query(value: &Value, path: &str) -> Result<Value, NikaError> {
67 let jp = JsonPath::parse(path).map_err(|e| NikaError::JsonPathUnsupported {
68 path: format!("{}: {}", path, e),
69 })?;
70 let results = jp.query(value);
71 let nodes: Vec<&Value> = results.all();
72 match nodes.len() {
73 0 => Ok(Value::Null),
74 1 => Ok(nodes[0].clone()),
75 _ => Ok(Value::Array(nodes.into_iter().cloned().collect())),
76 }
77}
78
79pub fn is_jsonpath(path: &str) -> bool {
84 if let Some(start) = path.find('[') {
86 let bracket_content = &path[start..];
87 if bracket_content.contains('*')
88 || bracket_content.contains('?')
89 || bracket_content.contains(':')
90 {
91 return true;
92 }
93 }
94 path.contains("..")
96}
97
98#[derive(Debug, Clone, PartialEq)]
104pub enum Segment {
105 Field(String),
107 Index(usize),
109}
110
111pub fn parse(path: &str) -> Result<Vec<Segment>, NikaError> {
122 let path = if let Some(stripped) = path.strip_prefix("$.") {
124 stripped
125 } else if path == "$" {
126 return Ok(vec![]);
127 } else {
128 path
129 };
130
131 if path.is_empty() {
132 return Ok(vec![]);
133 }
134
135 let mut segments = Vec::new();
136
137 for part in path.split('.') {
138 if part.is_empty() {
139 return Err(NikaError::JsonPathUnsupported {
140 path: path.to_string(),
141 });
142 }
143
144 if let Some(bracket_pos) = part.find('[') {
146 let field = &part[..bracket_pos];
147 if !field.is_empty() {
148 segments.push(Segment::Field(field.to_string()));
149 }
150
151 if !part.ends_with(']') {
152 return Err(NikaError::JsonPathUnsupported {
153 path: path.to_string(),
154 });
155 }
156
157 let index_str = &part[bracket_pos + 1..part.len() - 1];
158 let index: usize = index_str
159 .parse()
160 .map_err(|_| NikaError::JsonPathUnsupported {
161 path: path.to_string(),
162 })?;
163
164 segments.push(Segment::Index(index));
165 } else if let Ok(index) = part.parse::<usize>() {
166 segments.push(Segment::Index(index));
167 } else {
168 segments.push(Segment::Field(part.to_string()));
169 }
170 }
171
172 Ok(segments)
173}
174
175pub fn apply(value: &Value, segments: &[Segment]) -> Option<Value> {
179 let parsed;
182 let mut current = if let Some(v) = try_parse_json_str(value) {
183 parsed = v;
184 &parsed
185 } else {
186 value
187 };
188
189 for segment in segments {
190 current = match segment {
191 Segment::Field(name) => current.get(name)?,
192 Segment::Index(idx) => current.get(*idx)?,
193 };
194 }
195
196 Some(current.clone())
197}
198
199pub fn resolve(value: &Value, path: &str) -> Result<Option<Value>, NikaError> {
206 let segments = parse(path)?;
207 Ok(apply(value, &segments))
208}
209
210pub fn validate(path: &str) -> Result<(), NikaError> {
212 parse(path)?;
213 Ok(())
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use serde_json::json;
220
221 #[test]
226 fn parse_simple_path() {
227 let segments = parse("$.a.b.c").unwrap();
228 assert_eq!(
229 segments,
230 vec![
231 Segment::Field("a".to_string()),
232 Segment::Field("b".to_string()),
233 Segment::Field("c".to_string()),
234 ]
235 );
236 }
237
238 #[test]
239 fn parse_without_dollar() {
240 let segments = parse("a.b").unwrap();
241 assert_eq!(
242 segments,
243 vec![
244 Segment::Field("a".to_string()),
245 Segment::Field("b".to_string()),
246 ]
247 );
248 }
249
250 #[test]
251 fn parse_with_array_index() {
252 let segments = parse("$.items[0].name").unwrap();
253 assert_eq!(
254 segments,
255 vec![
256 Segment::Field("items".to_string()),
257 Segment::Index(0),
258 Segment::Field("name".to_string()),
259 ]
260 );
261 }
262
263 #[test]
264 fn parse_just_root() {
265 let segments = parse("$").unwrap();
266 assert!(segments.is_empty());
267 }
268
269 #[test]
270 fn apply_simple() {
271 let value = json!({"a": {"b": "value"}});
272 let segments = parse("$.a.b").unwrap();
273 let result = apply(&value, &segments);
274 assert_eq!(result, Some(json!("value")));
275 }
276
277 #[test]
278 fn apply_array_index() {
279 let value = json!({"items": ["first", "second", "third"]});
280 let segments = parse("$.items[1]").unwrap();
281 let result = apply(&value, &segments);
282 assert_eq!(result, Some(json!("second")));
283 }
284
285 #[test]
286 fn apply_nested_array() {
287 let value = json!({
288 "users": [
289 {"name": "Alice"},
290 {"name": "Bob"}
291 ]
292 });
293 let segments = parse("$.users[0].name").unwrap();
294 let result = apply(&value, &segments);
295 assert_eq!(result, Some(json!("Alice")));
296 }
297
298 #[test]
299 fn apply_missing_field() {
300 let value = json!({"a": 1});
301 let segments = parse("$.b").unwrap();
302 let result = apply(&value, &segments);
303 assert_eq!(result, None);
304 }
305
306 #[test]
307 fn resolve_shorthand() {
308 let value = json!({"price": {"currency": "EUR", "amount": 100}});
309 let result = resolve(&value, "$.price.currency").unwrap();
310 assert_eq!(result, Some(json!("EUR")));
311 }
312
313 #[test]
314 fn parse_numeric_index_as_dot() {
315 let segments = parse("items.0").unwrap();
316 assert_eq!(
317 segments,
318 vec![Segment::Field("items".to_string()), Segment::Index(0)]
319 );
320 }
321
322 #[test]
323 fn apply_numeric_index_as_dot() {
324 let value = json!({"items": ["first", "second"]});
325 let result = resolve(&value, "items.1").unwrap();
326 assert_eq!(result, Some(json!("second")));
327 }
328
329 #[test]
334 fn query_root() {
335 let value = json!({"name": "test"});
336 let result = query(&value, "$").unwrap();
337 assert_eq!(result, json!({"name": "test"}));
338 }
339
340 #[test]
341 fn query_single_field() {
342 let value = json!({"name": "test", "age": 30});
343 let result = query(&value, "$.name").unwrap();
344 assert_eq!(result, json!("test"));
345 }
346
347 #[test]
348 fn query_nested_field() {
349 let value = json!({"data": {"items": [1, 2, 3]}});
350 let result = query(&value, "$.data.items").unwrap();
351 assert_eq!(result, json!([1, 2, 3]));
352 }
353
354 #[test]
355 fn query_wildcard() {
356 let value = json!({
357 "items": [
358 {"name": "Alice"},
359 {"name": "Bob"},
360 {"name": "Charlie"}
361 ]
362 });
363 let result = query(&value, "$.items[*].name").unwrap();
364 assert_eq!(result, json!(["Alice", "Bob", "Charlie"]));
365 }
366
367 #[test]
368 fn query_array_index() {
369 let value = json!({"items": ["a", "b", "c"]});
370 let result = query(&value, "$.items[1]").unwrap();
371 assert_eq!(result, json!("b"));
372 }
373
374 #[test]
375 fn query_recursive_descent() {
376 let value = json!({
377 "a": {"email": "a@test.com"},
378 "b": {"nested": {"email": "b@test.com"}}
379 });
380 let result = query(&value, "$..email").unwrap();
381 assert!(result.is_array());
383 let arr = result.as_array().unwrap();
384 assert_eq!(arr.len(), 2);
385 assert!(arr.contains(&json!("a@test.com")));
386 assert!(arr.contains(&json!("b@test.com")));
387 }
388
389 #[test]
390 fn query_filter() {
391 let value = json!({
392 "items": [
393 {"name": "cheap", "price": 5},
394 {"name": "expensive", "price": 150},
395 {"name": "mid", "price": 50}
396 ]
397 });
398 let result = query(&value, "$.items[?@.price > 100]").unwrap();
399 assert_eq!(result, json!({"name": "expensive", "price": 150}));
400 }
401
402 #[test]
403 fn query_filter_multiple_results() {
404 let value = json!({
405 "items": [
406 {"name": "a", "price": 5},
407 {"name": "b", "price": 150},
408 {"name": "c", "price": 200}
409 ]
410 });
411 let result = query(&value, "$.items[?@.price > 100]").unwrap();
412 assert_eq!(
413 result,
414 json!([
415 {"name": "b", "price": 150},
416 {"name": "c", "price": 200}
417 ])
418 );
419 }
420
421 #[test]
422 fn query_no_match() {
423 let value = json!({"a": 1});
424 let result = query(&value, "$.missing").unwrap();
425 assert_eq!(result, Value::Null);
426 }
427
428 #[test]
429 fn query_slice() {
430 let value = json!({"items": [0, 1, 2, 3, 4, 5]});
431 let result = query(&value, "$.items[1:4]").unwrap();
432 assert_eq!(result, json!([1, 2, 3]));
433 }
434
435 #[test]
436 fn query_invalid_syntax() {
437 let result = query(&json!({}), "$.items[[invalid");
438 assert!(result.is_err());
439 }
440
441 #[test]
446 fn is_jsonpath_simple_paths() {
447 assert!(!is_jsonpath("$.a.b.c"));
448 assert!(!is_jsonpath("items[0].name"));
449 assert!(!is_jsonpath("a.b"));
450 }
451
452 #[test]
453 fn is_jsonpath_rich_expressions() {
454 assert!(is_jsonpath("$.items[*].name"));
455 assert!(is_jsonpath("$.items[?@.price > 100]"));
456 assert!(is_jsonpath("$..email"));
457 assert!(is_jsonpath("$.items[0:5]"));
458 assert!(is_jsonpath("$.items[:3]"));
459 }
460
461 #[test]
466 fn validate_valid_paths() {
467 assert!(validate("$.a.b").is_ok());
468 assert!(validate("items[0].name").is_ok());
469 assert!(validate("$").is_ok());
470 }
471
472 #[test]
473 fn validate_empty_segment() {
474 assert!(validate("a..b").is_err());
475 }
476}