Skip to content

Commit 4093064

Browse files
gquittetjstewmon
authored andcommitted
refactor(op): make JSON escape dot syntax more robust
See: #37 (review)
1 parent c9dab33 commit 4093064

File tree

1 file changed

+79
-34
lines changed

1 file changed

+79
-34
lines changed

src/op/data.rs

+79-34
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,30 @@ fn get_key(data: &Value, key: KeyType) -> Option<Value> {
227227
}
228228
}
229229

230-
fn split_path(path: &str) -> impl Iterator<Item = String> + '_ {
231-
let mut index = 0;
232-
return path
233-
.split(move |c: char| {
234-
if c == '.' && path.chars().nth(index - 1).unwrap() != '\\' {
235-
index += 1;
236-
return true;
237-
}
238-
index += 1;
239-
return false;
240-
})
241-
.map(|part| part.replace("\\.", "."));
230+
pub fn split_with_escape(input: &str, delimiter: char) -> Vec<String> {
231+
let mut result = Vec::new();
232+
let mut slice = String::new();
233+
let mut escape = false;
234+
235+
for c in input.chars() {
236+
if escape {
237+
slice.push(c);
238+
escape = false;
239+
} else if c == '\\' {
240+
escape = true;
241+
} else if c == delimiter {
242+
result.push(slice.clone());
243+
slice.clear();
244+
} else {
245+
slice.push(c);
246+
}
247+
}
248+
249+
if !slice.is_empty() {
250+
result.push(slice);
251+
}
252+
253+
result
242254
}
243255

244256
fn get_str_key<K: AsRef<str>>(data: &Value, key: K) -> Option<Value> {
@@ -249,30 +261,63 @@ fn get_str_key<K: AsRef<str>>(data: &Value, key: K) -> Option<Value> {
249261
match data {
250262
Value::Object(_) | Value::Array(_) | Value::String(_) => {
251263
// Exterior ref in case we need to make a new value in the match.
252-
split_path(k).fold(Some(data.clone()), |acc, i| match acc? {
253-
// If the current value is an object, try to get the value
254-
Value::Object(map) => map.get(&i).map(Value::clone),
255-
// If the current value is an array, we need an integer
256-
// index. If integer conversion fails, return None.
257-
Value::Array(arr) => i
258-
.parse::<i64>()
259-
.ok()
260-
.and_then(|i| get(&arr, i))
261-
.map(Value::clone),
262-
// Same deal if it's a string.
263-
Value::String(s) => {
264-
let s_chars: Vec<char> = s.chars().collect();
265-
i.parse::<i64>()
264+
split_with_escape(k, '.')
265+
.into_iter()
266+
.fold(Some(data.clone()), |acc, i| match acc? {
267+
// If the current value is an object, try to get the value
268+
Value::Object(map) => map.get(&i).map(Value::clone),
269+
// If the current value is an array, we need an integer
270+
// index. If integer conversion fails, return None.
271+
Value::Array(arr) => i
272+
.parse::<i64>()
266273
.ok()
267-
.and_then(|i| get(&s_chars, i))
268-
.map(|c| c.to_string())
269-
.map(Value::String)
270-
}
271-
// This handles cases where we've got an un-indexable
272-
// type or similar.
273-
_ => None,
274-
})
274+
.and_then(|i| get(&arr, i))
275+
.map(Value::clone),
276+
// Same deal if it's a string.
277+
Value::String(s) => {
278+
let s_chars: Vec<char> = s.chars().collect();
279+
i.parse::<i64>()
280+
.ok()
281+
.and_then(|i| get(&s_chars, i))
282+
.map(|c| c.to_string())
283+
.map(Value::String)
284+
}
285+
// This handles cases where we've got an un-indexable
286+
// type or similar.
287+
_ => None,
288+
})
275289
}
276290
_ => None,
277291
}
278292
}
293+
294+
#[cfg(test)]
295+
mod tests {
296+
use super::*;
297+
298+
// All the tests cases have been discussed here: https://github.com/Bestowinc/json-logic-rs/pull/37
299+
fn cases() -> Vec<(&'static str, Vec<&'static str>)> {
300+
vec![
301+
("", vec![]),
302+
("foo", vec!["foo"]),
303+
("foo.bar", vec!["foo", "bar"]),
304+
(r#"foo\.bar"#, vec!["foo.bar"]),
305+
(r#"foo\.bar.biz"#, vec!["foo.bar", "biz"]),
306+
(r#"foo\\.bar"#, vec!["foo\\", "bar"]),
307+
(r#"foo\\.bar\.biz"#, vec!["foo\\", "bar.biz"]),
308+
(r#"foo\\\.bar"#, vec!["foo\\.bar"]),
309+
(r#"foo\\\.bar.biz"#, vec!["foo\\.bar", "biz"]),
310+
(r#"foo\\bar"#, vec!["foo\\bar"]),
311+
(r#"foo\\bar.biz"#, vec!["foo\\bar", "biz"]),
312+
(r#"foo\\bar\.biz"#, vec!["foo\\bar.biz"]),
313+
(r#"foo\\bar\\.biz"#, vec!["foo\\bar\\", "biz"]),
314+
]
315+
}
316+
317+
#[test]
318+
fn test_split_with_escape() {
319+
cases()
320+
.into_iter()
321+
.for_each(|(input, exp)| assert_eq!(split_with_escape(&input, '.'), exp));
322+
}
323+
}

0 commit comments

Comments
 (0)