use serde_json::Value;
pub fn apply_input_path(input: &Value, path: Option<&str>) -> Value {
match path {
None | Some("$") => input.clone(),
Some(p) => resolve_path(input, p),
}
}
pub fn apply_output_path(output: &Value, path: Option<&str>) -> Value {
match path {
None | Some("$") => output.clone(),
Some(p) => resolve_path(output, p),
}
}
pub fn apply_result_path(input: &Value, result: &Value, path: Option<&str>) -> Value {
match path {
None | Some("$") => result.clone(),
Some("null") => input.clone(),
Some(p) => set_at_path(input, p, result),
}
}
pub fn resolve_path(root: &Value, path: &str) -> Value {
if path == "$" {
return root.clone();
}
let path = path.strip_prefix("$.").unwrap_or(path);
let mut current = root;
for segment in split_path_segments(path) {
match segment {
PathSegment::Field(name) => {
current = match current.get(name) {
Some(v) => v,
None => return Value::Null,
};
}
PathSegment::Index(name, idx) => {
current = match current.get(name) {
Some(v) => match v.get(idx) {
Some(v) => v,
None => return Value::Null,
},
None => return Value::Null,
};
}
}
}
current.clone()
}
fn set_at_path(root: &Value, path: &str, value: &Value) -> Value {
let mut result = root.clone();
let path = path.strip_prefix("$.").unwrap_or(path);
let segments: Vec<&str> = path.split('.').collect();
let mut current = &mut result;
for (i, segment) in segments.iter().enumerate() {
if i == segments.len() - 1 {
if let Some(obj) = current.as_object_mut() {
obj.insert(segment.to_string(), value.clone());
}
} else {
if current.get(*segment).is_none() {
if let Some(obj) = current.as_object_mut() {
obj.insert(segment.to_string(), serde_json::json!({}));
}
}
match current.get_mut(*segment) {
Some(v) => current = v,
None => return result, }
}
}
result
}
enum PathSegment<'a> {
Field(&'a str),
Index(&'a str, usize),
}
fn split_path_segments(path: &str) -> Vec<PathSegment<'_>> {
let mut segments = Vec::new();
for part in path.split('.') {
match parse_index_segment(part) {
Some(segment) => segments.push(segment),
None => segments.push(PathSegment::Field(part)),
}
}
segments
}
fn parse_index_segment(part: &str) -> Option<PathSegment<'_>> {
let open = part.find('[')?;
if !part.ends_with(']') {
return None;
}
let close = part.len() - 1;
let inner_start = open + 1;
if inner_start > close {
return None;
}
let idx_str = part.get(inner_start..close)?;
let name = part.get(..open)?;
let idx = idx_str.parse::<usize>().ok()?;
Some(PathSegment::Index(name, idx))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_resolve_path_root() {
let input = json!({"a": 1});
assert_eq!(resolve_path(&input, "$"), input);
}
#[test]
fn test_resolve_path_simple_field() {
let input = json!({"name": "hello", "value": 42});
assert_eq!(resolve_path(&input, "$.name"), json!("hello"));
assert_eq!(resolve_path(&input, "$.value"), json!(42));
}
#[test]
fn test_resolve_path_nested() {
let input = json!({"a": {"b": {"c": 99}}});
assert_eq!(resolve_path(&input, "$.a.b.c"), json!(99));
}
#[test]
fn test_resolve_path_missing() {
let input = json!({"a": 1});
assert_eq!(resolve_path(&input, "$.missing"), Value::Null);
}
#[test]
fn test_resolve_path_array_index() {
let input = json!({"items": [10, 20, 30]});
assert_eq!(resolve_path(&input, "$.items[0]"), json!(10));
assert_eq!(resolve_path(&input, "$.items[2]"), json!(30));
}
#[test]
fn test_apply_input_path_default() {
let input = json!({"x": 1});
assert_eq!(apply_input_path(&input, None), input);
assert_eq!(apply_input_path(&input, Some("$")), input);
}
#[test]
fn test_apply_result_path_default() {
let input = json!({"x": 1});
let result = json!({"y": 2});
assert_eq!(apply_result_path(&input, &result, None), result);
assert_eq!(apply_result_path(&input, &result, Some("$")), result);
}
#[test]
fn test_apply_result_path_null() {
let input = json!({"x": 1});
let result = json!({"y": 2});
assert_eq!(apply_result_path(&input, &result, Some("null")), input);
}
#[test]
fn test_apply_result_path_nested() {
let input = json!({"x": 1});
let result = json!("hello");
let output = apply_result_path(&input, &result, Some("$.result"));
assert_eq!(output, json!({"x": 1, "result": "hello"}));
}
#[test]
fn test_set_at_path_non_object_intermediate() {
let input = json!({"x": 42});
let result = json!("hello");
let output = apply_result_path(&input, &result, Some("$.x.nested"));
assert_eq!(output, json!({"x": 42}));
}
#[test]
fn test_apply_output_path() {
let output = json!({"a": 1, "b": 2});
assert_eq!(apply_output_path(&output, Some("$.a")), json!(1));
assert_eq!(apply_output_path(&output, None), output);
}
#[test]
fn test_resolve_path_unclosed_bracket_does_not_panic() {
let input = json!({"arr": [1, 2, 3]});
assert_eq!(resolve_path(&input, "$.arr["), Value::Null);
}
#[test]
fn test_resolve_path_multibyte_after_bracket_does_not_panic() {
let input = json!({"x": [1, 2, 3]});
assert_eq!(resolve_path(&input, "$.x[é"), Value::Null);
assert_eq!(resolve_path(&input, "$.x[é]"), Value::Null);
}
#[test]
fn test_resolve_path_empty_brackets_do_not_panic() {
let input = json!({"x": [1, 2, 3]});
assert_eq!(resolve_path(&input, "$.x[]"), Value::Null);
}
#[test]
fn test_resolve_path_bracket_only_segment() {
let input = json!({"a": 1});
assert_eq!(resolve_path(&input, "$.["), Value::Null);
assert_eq!(resolve_path(&input, "$.]"), Value::Null);
}
#[test]
fn test_split_path_segments_well_formed_index_still_works() {
let input = json!({"items": [10, 20, 30]});
assert_eq!(resolve_path(&input, "$.items[1]"), json!(20));
let nested = json!({"a": {"b": [{"c": 7}]}});
assert_eq!(resolve_path(&nested, "$.a.b[0].c"), json!(7));
}
#[test]
fn test_apply_input_path_malformed_does_not_panic() {
let input = json!({"arr": [1, 2, 3]});
let _ = apply_input_path(&input, Some("$.arr["));
let _ = apply_output_path(&input, Some("$.x[é"));
}
}