use crate::json_node::{JsonNode, KeyValuePair};
use crate::types::{Diagnostic, Span};
use serde_json::Value;
use serde_json_path::JsonPath;
fn node_to_value(node: &JsonNode) -> Value {
match node {
JsonNode::Null(_) => Value::Null,
JsonNode::Bool(b, _) => Value::Bool(*b),
JsonNode::Number(_, raw, _) => raw
.parse::<serde_json::Number>()
.map(Value::Number)
.unwrap_or(Value::Null),
JsonNode::String(s, _) => Value::String(s.clone()),
JsonNode::Array(items, _) => Value::Array(items.iter().map(node_to_value).collect()),
JsonNode::Object(pairs, _) => {
let map = pairs
.iter()
.map(|kv| (kv.key.clone(), node_to_value(&kv.value)))
.collect();
Value::Object(map)
}
}
}
fn value_to_node(value: &Value) -> JsonNode {
let span = Span::default();
match value {
Value::Null => JsonNode::Null(span),
Value::Bool(b) => JsonNode::Bool(*b, span),
Value::Number(n) => JsonNode::Number(n.as_f64().unwrap_or(0.0), n.to_string(), span),
Value::String(s) => JsonNode::String(s.clone(), span),
Value::Array(items) => JsonNode::Array(items.iter().map(value_to_node).collect(), span),
Value::Object(map) => {
let pairs = map
.iter()
.map(|(k, v)| KeyValuePair {
key: k.clone(),
value: value_to_node(v),
})
.collect();
JsonNode::Object(pairs, span)
}
}
}
pub fn evaluate_path(node: &JsonNode, path: &str) -> Result<Vec<JsonNode>, String> {
let json_path = JsonPath::parse(path).map_err(|e| format!("Invalid JSONPath '{path}': {e}"))?;
let value = node_to_value(node);
let node_list = json_path.query(&value);
let results = node_list.iter().map(|v| value_to_node(v)).collect();
Ok(results)
}
pub fn diagnostics_for_path(path: &str) -> Vec<Diagnostic> {
match JsonPath::parse(path) {
Ok(_) => vec![],
Err(e) => vec![Diagnostic {
span: Span {
start: 0,
end: path.len(),
},
message: format!("Invalid JSONPath: {e}"),
}],
}
}
#[cfg(test)]
mod jsonpath_tests {
use super::*;
use crate::parser::parse;
fn parse_doc(src: &str) -> JsonNode {
parse(src).expect("test document must be valid JSON")
}
#[test]
fn test_jsonpath_root_selector() {
let node = parse_doc(r#"{"a": 1}"#);
let results = evaluate_path(&node, "$").unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], JsonNode::Object(_, _)));
}
#[test]
fn test_jsonpath_dot_key() {
let node = parse_doc(r#"{"name": "Alice", "age": 30}"#);
let results = evaluate_path(&node, "$.name").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0],
JsonNode::String("Alice".into(), Span::default())
);
}
#[test]
fn test_jsonpath_array_index() {
let node = parse_doc(r#"{"arr": [10, 20, 30]}"#);
let results = evaluate_path(&node, "$.arr[0]").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0],
JsonNode::Number(10.0, "10".into(), Span::default())
);
}
#[test]
fn test_jsonpath_wildcard_array() {
let node = parse_doc(r#"{"users": [{"name": "Alice"}, {"name": "Bob"}]}"#);
let results = evaluate_path(&node, "$.users[*].name").unwrap();
assert_eq!(results.len(), 2);
assert_eq!(
results[0],
JsonNode::String("Alice".into(), Span::default())
);
assert_eq!(results[1], JsonNode::String("Bob".into(), Span::default()));
}
#[test]
fn test_jsonpath_recursive_descent() {
let node = parse_doc(r#"{"a": {"b": {"name": "deep"}}, "name": "root"}"#);
let results = evaluate_path(&node, "$..name").unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_jsonpath_filter_expression() {
let node = parse_doc(r#"{"items": [{"val": 1}, {"val": 5}, {"val": 3}]}"#);
let results = evaluate_path(&node, "$.items[?@.val > 2]").unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_jsonpath_no_match_returns_empty() {
let node = parse_doc(r#"{"a": 1}"#);
let results = evaluate_path(&node, "$.nonexistent").unwrap();
assert!(results.is_empty());
}
#[test]
fn test_jsonpath_invalid_path_returns_err() {
let node = parse_doc(r#"{"a": 1}"#);
let result = evaluate_path(&node, "not a valid path");
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid JSONPath"));
}
#[test]
fn test_jsonpath_null_value() {
let node = parse_doc(r#"{"x": null}"#);
let results = evaluate_path(&node, "$.x").unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], JsonNode::Null(_)));
}
#[test]
fn test_jsonpath_bool_value() {
let node = parse_doc(r#"{"flag": true}"#);
let results = evaluate_path(&node, "$.flag").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], JsonNode::Bool(true, Span::default()));
}
#[test]
fn test_diagnostics_valid_path_is_empty() {
let diags = diagnostics_for_path("$.store.book[*].author");
assert!(diags.is_empty());
}
#[test]
fn test_diagnostics_root_only_is_valid() {
let diags = diagnostics_for_path("$");
assert!(diags.is_empty());
}
#[test]
fn test_diagnostics_invalid_path_returns_diagnostic() {
let input = "INVALID";
let diags = diagnostics_for_path(input);
assert_eq!(diags.len(), 1);
assert!(diags[0].message.contains("Invalid JSONPath"));
assert_eq!(diags[0].span.start, 0);
assert_eq!(diags[0].span.end, input.len());
}
#[test]
fn test_diagnostics_empty_string_returns_diagnostic() {
let diags = diagnostics_for_path("");
assert!(!diags.is_empty());
}
#[test]
fn test_node_to_value_and_back_object() {
let node = parse_doc(r#"{"key": "value", "num": 42}"#);
let value = node_to_value(&node);
let roundtrip = value_to_node(&value);
assert!(matches!(roundtrip, JsonNode::Object(_, _)));
if let JsonNode::Object(pairs, _) = &roundtrip {
assert_eq!(pairs.len(), 2);
assert_eq!(pairs[0].key, "key");
}
}
}