Skip to main content

wedb_embed/api/json/path/
parser.rs

1use std::borrow::Cow;
2
3use super::ast::{FilterExpr, FilterOp, PathSegment, SliceIndex};
4use crate::error::{Error, Result};
5
6/// Parses JSONPath query string into a list of borrowed path segments.
7/// 解析 JSONPath 表达式为片段列表(零堆分配借用切片)
8pub fn parse_json_path<'a>(path: &'a str) -> Result<Vec<PathSegment<'a>>> {
9  let s = path.trim();
10  if s.is_empty() || s == "$" || s == "." {
11    return Ok(vec![PathSegment::Root]);
12  }
13
14  let bytes = s.as_bytes();
15  let mut i = 0;
16  let mut segments = Vec::new();
17
18  if bytes[0] == b'$' {
19    segments.push(PathSegment::Root);
20    i += 1;
21  }
22
23  while i < bytes.len() {
24    if bytes[i] == b'.' {
25      i += 1;
26      if i >= bytes.len() {
27        return Err(Error::invalid_data("Invalid JSONPath: trailing dot"));
28      }
29      if bytes[i] == b'.' {
30        // 递归下降 ..
31        i += 1;
32        if i >= bytes.len() {
33          return Err(Error::invalid_data(
34            "Invalid JSONPath: trailing recursive descent '..'",
35          ));
36        }
37        if bytes[i] == b'*' {
38          segments.push(PathSegment::Recursive(Box::new(PathSegment::Wildcard)));
39          i += 1;
40          continue;
41        }
42        if bytes[i] == b'[' {
43          let bracket_seg = parse_bracket(s, &mut i)?;
44          segments.push(PathSegment::Recursive(Box::new(bracket_seg)));
45          continue;
46        }
47        let start = i;
48        while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
49          i += 1;
50        }
51        let name = s[start..i].trim();
52        if name.is_empty() {
53          return Err(Error::invalid_data(
54            "Invalid JSONPath: empty identifier after '..'",
55          ));
56        }
57        segments.push(PathSegment::Recursive(Box::new(PathSegment::Field(
58          Cow::Borrowed(name),
59        ))));
60        continue;
61      }
62      if bytes[i] == b'*' {
63        segments.push(PathSegment::Wildcard);
64        i += 1;
65        continue;
66      }
67      if bytes[i] == b'[' {
68        let bracket_seg = parse_bracket(s, &mut i)?;
69        segments.push(bracket_seg);
70        continue;
71      }
72      let start = i;
73      while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
74        i += 1;
75      }
76      let name = s[start..i].trim();
77      if name.is_empty() {
78        return Err(Error::invalid_data(
79          "Invalid JSONPath: empty identifier after '.'",
80        ));
81      }
82      if name == "*" {
83        segments.push(PathSegment::Wildcard);
84      } else {
85        segments.push(PathSegment::Field(Cow::Borrowed(name)));
86      }
87    } else if bytes[i] == b'[' {
88      let bracket_seg = parse_bracket(s, &mut i)?;
89      segments.push(bracket_seg);
90    } else {
91      let start = i;
92      while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
93        i += 1;
94      }
95      let name = s[start..i].trim();
96      if name.is_empty() {
97        return Err(Error::invalid_data("Invalid JSONPath: unexpected token"));
98      }
99      if name == "*" {
100        segments.push(PathSegment::Wildcard);
101      } else {
102        segments.push(PathSegment::Field(Cow::Borrowed(name)));
103      }
104    }
105  }
106
107  if segments.is_empty() {
108    Ok(vec![PathSegment::Root])
109  } else {
110    Ok(segments)
111  }
112}
113
114pub(crate) fn parse_bracket<'a>(s: &'a str, i: &mut usize) -> Result<PathSegment<'a>> {
115  let bytes = s.as_bytes();
116  if *i >= bytes.len() || bytes[*i] != b'[' {
117    return Err(Error::invalid_data("Expected '['"));
118  }
119  *i += 1;
120  let start = *i;
121  let mut depth = 1;
122  let mut in_single_quote = false;
123  let mut in_double_quote = false;
124
125  let mut i_inner = *i;
126  while i_inner < bytes.len() && depth > 0 {
127    match bytes[i_inner] {
128      b'\\' if in_single_quote || in_double_quote => {
129        i_inner += 1; // 跳过转义字符
130      }
131      b'\'' if !in_double_quote => {
132        in_single_quote = !in_single_quote;
133      }
134      b'"' if !in_single_quote => {
135        in_double_quote = !in_double_quote;
136      }
137      b'[' if !in_single_quote && !in_double_quote => {
138        depth += 1;
139      }
140      b']' if !in_single_quote && !in_double_quote => {
141        depth -= 1;
142      }
143      _ => {}
144    }
145    if depth > 0 {
146      i_inner += 1;
147    }
148  }
149  *i = i_inner;
150
151  if depth > 0 || *i >= bytes.len() || bytes[*i] != b']' {
152    return Err(Error::invalid_data("Invalid JSONPath: unclosed bracket"));
153  }
154
155  let inner = s[start..*i].trim();
156  *i += 1; // 跳过 ']'
157
158  parse_bracket_content(inner)
159}
160
161pub(crate) fn parse_bracket_content<'a>(inner: &'a str) -> Result<PathSegment<'a>> {
162  let inner = inner.trim();
163  if inner.is_empty() || inner == "*" {
164    return Ok(PathSegment::Wildcard);
165  }
166
167  // 过滤表达式 [?(...)]
168  if let Some(stripped) = inner.strip_prefix('?') {
169    let expr_str = stripped
170      .trim()
171      .trim_start_matches('(')
172      .trim_end_matches(')')
173      .trim();
174    if let Some(filter) = parse_filter_expr(expr_str) {
175      return Ok(PathSegment::Filter(filter));
176    }
177    return Err(Error::invalid_data("Invalid filter expression in JSONPath"));
178  }
179
180  // 多选择器逗号分割 [a, b, c] 或 [0, 1]
181  if inner.contains(',') {
182    let parts = split_bracket_parts(inner);
183    if parts.len() > 1 {
184      let mut slice_indices = Vec::new();
185      let mut field_names = Vec::new();
186      let mut all_indices = true;
187      let mut all_fields = true;
188
189      for &part in &parts {
190        let p = part.trim();
191        if let Some(slice_idx) = parse_single_slice(p) {
192          slice_indices.push(slice_idx);
193          all_fields = false;
194        } else {
195          let unquoted = unquote_cow(p);
196          field_names.push(unquoted);
197          all_indices = false;
198        }
199      }
200
201      if all_indices {
202        return Ok(PathSegment::MultiIndex(slice_indices));
203      }
204      if all_fields {
205        return Ok(PathSegment::MultiField(field_names));
206      }
207      return Ok(PathSegment::MultiField(
208        parts.into_iter().map(unquote_cow).collect(),
209      ));
210    }
211  }
212
213  // 单个切片或索引
214  if let Some(slice_idx) = parse_single_slice(inner) {
215    match slice_idx {
216      SliceIndex::Index(idx) => return Ok(PathSegment::Index(idx)),
217      SliceIndex::Slice { start, stop, step } => {
218        return Ok(PathSegment::MultiIndex(vec![SliceIndex::Slice {
219          start,
220          stop,
221          step,
222        }]));
223      }
224    }
225  }
226
227  // 单个字段名(支持引号或无引号)
228  let unquoted = unquote_cow(inner);
229  if !unquoted.is_empty() {
230    Ok(PathSegment::Field(unquoted))
231  } else {
232    Err(Error::invalid_data("Invalid empty bracket content"))
233  }
234}
235
236pub(crate) fn split_bracket_parts(s: &str) -> Vec<&str> {
237  let mut parts = Vec::new();
238  let mut start = 0;
239  let mut in_single_quote = false;
240  let mut in_double_quote = false;
241  let bytes = s.as_bytes();
242  let mut i = 0;
243
244  while i < bytes.len() {
245    match bytes[i] {
246      b'\\' if in_single_quote || in_double_quote => {
247        i += 1; // 跳过转义字符
248      }
249      b'\'' if !in_double_quote => {
250        in_single_quote = !in_single_quote;
251      }
252      b'"' if !in_single_quote => {
253        in_double_quote = !in_double_quote;
254      }
255      b',' if !in_single_quote && !in_double_quote => {
256        parts.push(s[start..i].trim());
257        start = i + 1;
258      }
259      _ => {}
260    }
261    i += 1;
262  }
263  let rest = s[start..].trim();
264  if !rest.is_empty() {
265    parts.push(rest);
266  }
267  parts
268}
269
270#[inline]
271pub(crate) fn unquote_cow(s: &str) -> Cow<'_, str> {
272  let s = s.trim();
273  if (s.starts_with('\'') && s.ends_with('\'') && s.len() >= 2)
274    || (s.starts_with('"') && s.ends_with('"') && s.len() >= 2)
275  {
276    let inner = &s[1..s.len() - 1];
277    if inner.contains('\\') {
278      Cow::Owned(
279        inner
280          .replace("\\'", "'")
281          .replace("\\\"", "\"")
282          .replace("\\\\", "\\"),
283      )
284    } else {
285      Cow::Borrowed(inner)
286    }
287  } else {
288    Cow::Borrowed(s)
289  }
290}
291
292pub(crate) fn parse_single_slice(s: &str) -> Option<SliceIndex> {
293  let s = s.trim();
294  if s.contains(':') {
295    let mut parts = s.split(':');
296    let p0 = parts.next()?;
297    let p1 = parts.next()?;
298    let p2 = parts.next();
299    if parts.next().is_some() {
300      return None;
301    }
302    let parse_part = |p: &str| -> Option<Option<isize>> {
303      let t = p.trim();
304      if t.is_empty() {
305        Some(None)
306      } else {
307        t.parse::<isize>().ok().map(Some)
308      }
309    };
310    let start = parse_part(p0)?;
311    let stop = parse_part(p1)?;
312    let step = match p2 {
313      Some(p) => parse_part(p)?,
314      None => None,
315    };
316    Some(SliceIndex::Slice { start, stop, step })
317  } else {
318    s.parse::<isize>().ok().map(SliceIndex::Index)
319  }
320}
321
322pub(crate) fn parse_filter_expr(s: &str) -> Option<FilterExpr<'_>> {
323  let s = s.trim();
324  if let Some(stripped) = s.strip_prefix('!') {
325    let inner = stripped.trim().strip_prefix('@')?;
326    let inner = inner.trim().strip_prefix('.').unwrap_or(inner);
327    let path = inner.split('.').map(Cow::Borrowed).collect();
328    return Some(FilterExpr {
329      path,
330      op: FilterOp::NotExists,
331    });
332  }
333
334  for op_str in &["==", "!=", "<=", ">=", "<", ">"] {
335    if let Some(idx) = s.find(op_str) {
336      let left = s[..idx].trim();
337      let right = s[idx + op_str.len()..].trim();
338
339      let left_path = left
340        .strip_prefix('@')?
341        .trim()
342        .strip_prefix('.')
343        .unwrap_or(left)
344        .split('.')
345        .map(Cow::Borrowed)
346        .collect();
347
348      let right_val = if (right.starts_with('\'') && right.ends_with('\''))
349        || (right.starts_with('"') && right.ends_with('"'))
350      {
351        sonic_rs::json!(unquote_cow(right).as_ref())
352      } else if right == "true" {
353        sonic_rs::json!(true)
354      } else if right == "false" {
355        sonic_rs::json!(false)
356      } else if right == "null" {
357        sonic_rs::json!(null)
358      } else if let Ok(num) = right.parse::<f64>() {
359        if num.fract() == 0.0 && num >= (i64::MIN as f64) && num <= (i64::MAX as f64) {
360          sonic_rs::json!(num as i64)
361        } else {
362          sonic_rs::json!(num)
363        }
364      } else {
365        sonic_rs::json!(right)
366      };
367
368      let op = match *op_str {
369        "==" => FilterOp::Eq(right_val),
370        "!=" => FilterOp::Ne(right_val),
371        "<" => FilterOp::Lt(right.parse().unwrap_or(0.0)),
372        "<=" => FilterOp::Le(right.parse().unwrap_or(0.0)),
373        ">" => FilterOp::Gt(right.parse().unwrap_or(0.0)),
374        ">=" => FilterOp::Ge(right.parse().unwrap_or(0.0)),
375        _ => FilterOp::Exists,
376      };
377
378      return Some(FilterExpr {
379        path: left_path,
380        op,
381      });
382    }
383  }
384
385  let left = s.strip_prefix('@')?;
386  let left = left.trim().strip_prefix('.').unwrap_or(left);
387  let path = left.split('.').map(Cow::Borrowed).collect();
388  Some(FilterExpr {
389    path,
390    op: FilterOp::Exists,
391  })
392}