use serde_json::Value;
use serde_json_path::JsonPath;
use thiserror::Error;
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum JsonPathError {
#[error("JSONPath parse error: {0}")]
Parse(String),
}
pub fn evaluate_path(path: &str, value: &Value) -> Result<Vec<Value>, JsonPathError> {
let parsed = JsonPath::parse(path).map_err(|e| JsonPathError::Parse(e.to_string()))?;
let node_list = parsed.query(value);
Ok(node_list.all().into_iter().cloned().collect())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn root_path_returns_whole_value() {
let value = json!({"a": 1, "b": 2});
let got = evaluate_path("$", &value).expect("root is always valid");
assert_eq!(got, vec![value.clone()]);
}
#[test]
fn simple_key_access_returns_matching_scalar() {
let value = json!({"name": "alice"});
let got = evaluate_path("$.name", &value).unwrap();
assert_eq!(got, vec![json!("alice")]);
}
#[test]
fn nested_key_access_returns_leaf_scalar() {
let value = json!({"user": {"profile": {"email": "a@b.com"}}});
let got = evaluate_path("$.user.profile.email", &value).unwrap();
assert_eq!(got, vec![json!("a@b.com")]);
}
#[test]
fn array_index_returns_single_element() {
let value = json!({"items": [10, 20, 30]});
let got = evaluate_path("$.items[1]", &value).unwrap();
assert_eq!(got, vec![json!(20)]);
}
#[test]
fn array_wildcard_returns_every_element_in_order() {
let value = json!({"items": [1, 2, 3]});
let got = evaluate_path("$.items[*]", &value).unwrap();
assert_eq!(got, vec![json!(1), json!(2), json!(3)]);
}
#[test]
fn projected_field_over_array_preserves_order() {
let value = json!({"items": [{"id": 1}, {"id": 2}, {"id": 3}]});
let got = evaluate_path("$.items[*].id", &value).unwrap();
assert_eq!(got, vec![json!(1), json!(2), json!(3)]);
}
#[test]
fn filter_expression_narrows_array() {
let value = json!({"items": [{"id": 1, "ok": true}, {"id": 2, "ok": false}, {"id": 3, "ok": true}]});
let got = evaluate_path("$.items[?(@.ok == true)].id", &value).unwrap();
assert_eq!(got, vec![json!(1), json!(3)]);
}
#[test]
fn not_found_returns_empty_vec_not_error() {
let value = json!({"present": 1});
let got = evaluate_path("$.missing", &value).unwrap();
assert_eq!(got, Vec::<Value>::new());
}
#[test]
fn invalid_path_returns_parse_error() {
let value = json!({});
let err = evaluate_path("$.[not valid json path]", &value).unwrap_err();
assert!(
matches!(err, JsonPathError::Parse(_)),
"expected Parse error, got {err:?}"
);
}
#[test]
fn empty_string_path_returns_parse_error() {
let value = json!({});
let err = evaluate_path("", &value).unwrap_err();
assert!(matches!(err, JsonPathError::Parse(_)));
}
#[test]
fn non_root_anchored_path_returns_parse_error() {
let value = json!({"foo": 1});
let err = evaluate_path("foo", &value).unwrap_err();
assert!(matches!(err, JsonPathError::Parse(_)));
}
#[test]
fn preserves_json_types_across_matches() {
let value = json!({"mix": [1, "two", true, null, 3.5]});
let got = evaluate_path("$.mix[*]", &value).unwrap();
assert_eq!(
got,
vec![json!(1), json!("two"), json!(true), json!(null), json!(3.5)]
);
}
#[test]
fn returned_values_are_owned_not_aliased() {
let value = json!({"a": {"b": [1, 2]}});
let got = evaluate_path("$.a.b", &value).unwrap();
assert_eq!(got, vec![json!([1, 2])]);
let value2 = json!({"a": {"b": [99]}});
let got2 = evaluate_path("$.a.b", &value2).unwrap();
assert_eq!(got2, vec![json!([99])]);
assert_eq!(got, vec![json!([1, 2])]);
}
#[test]
fn parse_error_message_is_not_empty() {
let err = evaluate_path("$.[", &json!({})).unwrap_err();
let JsonPathError::Parse(msg) = err;
assert!(!msg.is_empty(), "Parse error message must be non-empty");
}
}