ricecoder_external_lsp/mapping/
json_path.rs

1//! JSON path expression parser
2//!
3//! Supports parsing and evaluating JSON path expressions like:
4//! - `$.result.items` - nested field access
5//! - `$.result.items[*].label` - array indexing with wildcard
6//! - `$[0].range.start.line` - array indexing with specific index
7//! - `$.result` - simple field access
8
9use crate::error::{ExternalLspError, Result};
10use serde_json::Value;
11use std::str::FromStr;
12
13/// A segment of a JSON path expression
14#[derive(Debug, Clone, PartialEq, Eq)]
15enum PathSegment {
16    /// Root selector ($)
17    Root,
18    /// Field access (.field)
19    Field(String),
20    /// Array index ([0])
21    Index(usize),
22    /// Array wildcard ([*])
23    Wildcard,
24}
25
26/// Parses and evaluates JSON path expressions
27#[derive(Debug, Clone)]
28pub struct JsonPathParser {
29    segments: Vec<PathSegment>,
30}
31
32impl JsonPathParser {
33    /// Create a new JSON path parser from an expression string
34    ///
35    /// # Examples
36    ///
37    /// ```ignore
38    /// let parser = JsonPathParser::parse("$.result.items[*].label")?;
39    /// ```
40    pub fn parse(expression: &str) -> Result<Self> {
41        let segments = Self::parse_expression(expression)?;
42        Ok(Self { segments })
43    }
44
45    /// Parse a JSON path expression into segments
46    fn parse_expression(expr: &str) -> Result<Vec<PathSegment>> {
47        let expr = expr.trim();
48
49        if !expr.starts_with('$') {
50            return Err(ExternalLspError::JsonPathError(
51                "JSON path must start with $".to_string(),
52            ));
53        }
54
55        let mut segments = vec![PathSegment::Root];
56        let mut remaining = &expr[1..];
57
58        while !remaining.is_empty() {
59            if remaining.starts_with('.') {
60                // Field access
61                remaining = &remaining[1..];
62
63                // Find the end of the field name
64                let end = remaining
65                    .find(['.', '['])
66                    .unwrap_or(remaining.len());
67
68                if end == 0 {
69                    return Err(ExternalLspError::JsonPathError(
70                        "Empty field name in JSON path".to_string(),
71                    ));
72                }
73
74                let field = remaining[..end].to_string();
75                segments.push(PathSegment::Field(field));
76                remaining = &remaining[end..];
77            } else if remaining.starts_with('[') {
78                // Array access
79                let end = remaining.find(']').ok_or_else(|| {
80                    ExternalLspError::JsonPathError("Unclosed bracket in JSON path".to_string())
81                })?;
82
83                let index_str = &remaining[1..end];
84
85                if index_str == "*" {
86                    segments.push(PathSegment::Wildcard);
87                } else {
88                    let index: usize = index_str.parse().map_err(|_| {
89                        ExternalLspError::JsonPathError(format!(
90                            "Invalid array index: {}",
91                            index_str
92                        ))
93                    })?;
94                    segments.push(PathSegment::Index(index));
95                }
96
97                remaining = &remaining[end + 1..];
98            } else {
99                return Err(ExternalLspError::JsonPathError(format!(
100                    "Unexpected character in JSON path: {}",
101                    remaining.chars().next().unwrap_or('?')
102                )));
103            }
104        }
105
106        Ok(segments)
107    }
108
109    /// Extract values from a JSON object using this path
110    ///
111    /// Returns a vector of values. For paths with wildcards, may return multiple values.
112    pub fn extract(&self, value: &Value) -> Result<Vec<Value>> {
113        Self::extract_recursive(value, &self.segments, 0)
114    }
115
116    /// Recursively extract values following the path segments
117    fn extract_recursive(value: &Value, segments: &[PathSegment], index: usize) -> Result<Vec<Value>> {
118        if index >= segments.len() {
119            return Ok(vec![value.clone()]);
120        }
121
122        match &segments[index] {
123            PathSegment::Root => {
124                // Root is always the current value
125                Self::extract_recursive(value, segments, index + 1)
126            }
127            PathSegment::Field(field) => {
128                let next_value = value
129                    .get(field)
130                    .ok_or_else(|| {
131                        ExternalLspError::JsonPathError(format!(
132                            "Field '{}' not found in JSON object",
133                            field
134                        ))
135                    })?;
136
137                Self::extract_recursive(next_value, segments, index + 1)
138            }
139            PathSegment::Index(idx) => {
140                let array = value.as_array().ok_or_else(|| {
141                    ExternalLspError::JsonPathError(format!(
142                        "Expected array at index access, got {}",
143                        Self::value_type_name(value)
144                    ))
145                })?;
146
147                let next_value = array.get(*idx).ok_or_else(|| {
148                    ExternalLspError::JsonPathError(format!(
149                        "Array index {} out of bounds (length: {})",
150                        idx,
151                        array.len()
152                    ))
153                })?;
154
155                Self::extract_recursive(next_value, segments, index + 1)
156            }
157            PathSegment::Wildcard => {
158                let array = value.as_array().ok_or_else(|| {
159                    ExternalLspError::JsonPathError(format!(
160                        "Expected array for wildcard, got {}",
161                        Self::value_type_name(value)
162                    ))
163                })?;
164
165                let mut results = Vec::new();
166                for item in array {
167                    let mut item_results = Self::extract_recursive(item, segments, index + 1)?;
168                    results.append(&mut item_results);
169                }
170
171                Ok(results)
172            }
173        }
174    }
175
176    /// Extract a single value from a JSON object, returning an error if not found
177    pub fn extract_single(&self, value: &Value) -> Result<Value> {
178        let results = self.extract(value)?;
179
180        if results.is_empty() {
181            return Err(ExternalLspError::JsonPathError(
182                "JSON path returned no results".to_string(),
183            ));
184        }
185
186        if results.len() > 1 {
187            return Err(ExternalLspError::JsonPathError(
188                "JSON path returned multiple results, expected single value".to_string(),
189            ));
190        }
191
192        Ok(results[0].clone())
193    }
194
195    /// Get the type name of a JSON value
196    fn value_type_name(value: &Value) -> &'static str {
197        match value {
198            Value::Null => "null",
199            Value::Bool(_) => "boolean",
200            Value::Number(_) => "number",
201            Value::String(_) => "string",
202            Value::Array(_) => "array",
203            Value::Object(_) => "object",
204        }
205    }
206}
207
208impl FromStr for JsonPathParser {
209    type Err = ExternalLspError;
210
211    fn from_str(s: &str) -> Result<Self> {
212        Self::parse(s)
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use serde_json::json;
220
221    #[test]
222    fn test_parse_simple_field() {
223        let parser = JsonPathParser::parse("$.result").unwrap();
224        let json = json!({"result": "value"});
225        let results = parser.extract(&json).unwrap();
226        assert_eq!(results.len(), 1);
227        assert_eq!(results[0], json!("value"));
228    }
229
230    #[test]
231    fn test_parse_nested_field() {
232        let parser = JsonPathParser::parse("$.result.items").unwrap();
233        let json = json!({"result": {"items": [1, 2, 3]}});
234        let results = parser.extract(&json).unwrap();
235        assert_eq!(results.len(), 1);
236        assert_eq!(results[0], json!([1, 2, 3]));
237    }
238
239    #[test]
240    fn test_parse_array_index() {
241        let parser = JsonPathParser::parse("$.items[0]").unwrap();
242        let json = json!({"items": ["a", "b", "c"]});
243        let results = parser.extract(&json).unwrap();
244        assert_eq!(results.len(), 1);
245        assert_eq!(results[0], json!("a"));
246    }
247
248    #[test]
249    fn test_parse_wildcard() {
250        let parser = JsonPathParser::parse("$.items[*].label").unwrap();
251        let json = json!({
252            "items": [
253                {"label": "first"},
254                {"label": "second"},
255                {"label": "third"}
256            ]
257        });
258        let results = parser.extract(&json).unwrap();
259        assert_eq!(results.len(), 3);
260        assert_eq!(results[0], json!("first"));
261        assert_eq!(results[1], json!("second"));
262        assert_eq!(results[2], json!("third"));
263    }
264
265    #[test]
266    fn test_parse_missing_field() {
267        let parser = JsonPathParser::parse("$.missing").unwrap();
268        let json = json!({"result": "value"});
269        let result = parser.extract(&json);
270        assert!(result.is_err());
271    }
272
273    #[test]
274    fn test_parse_invalid_expression() {
275        let result = JsonPathParser::parse("invalid");
276        assert!(result.is_err());
277    }
278
279    #[test]
280    fn test_parse_unclosed_bracket() {
281        let result = JsonPathParser::parse("$.items[0");
282        assert!(result.is_err());
283    }
284
285    #[test]
286    fn test_extract_single_success() {
287        let parser = JsonPathParser::parse("$.result").unwrap();
288        let json = json!({"result": "value"});
289        let result = parser.extract_single(&json).unwrap();
290        assert_eq!(result, json!("value"));
291    }
292
293    #[test]
294    fn test_extract_single_multiple_results() {
295        let parser = JsonPathParser::parse("$.items[*]").unwrap();
296        let json = json!({"items": [1, 2, 3]});
297        let result = parser.extract_single(&json);
298        assert!(result.is_err());
299    }
300}