wedb_embed/api/json/path/
parser.rs1use std::borrow::Cow;
2
3use super::ast::{FilterExpr, FilterOp, PathSegment, SliceIndex};
4use crate::error::{Error, Result};
5
6pub 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 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; }
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; 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 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 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 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 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; }
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}