diff --git a/src/op/data.rs b/src/op/data.rs index a3ed996..a4e8be3 100644 --- a/src/op/data.rs +++ b/src/op/data.rs @@ -227,18 +227,30 @@ fn get_key(data: &Value, key: KeyType) -> Option { } } -fn split_path(path: &str) -> impl Iterator + '_ { - let mut index = 0; - return path - .split(move |c: char| { - if c == '.' && path.chars().nth(index - 1).unwrap() != '\\' { - index += 1; - return true; - } - index += 1; - return false; - }) - .map(|part| part.replace("\\.", ".")); +pub fn split_with_escape(input: &str, delimiter: char) -> Vec { + let mut result = Vec::new(); + let mut current = String::new(); + let mut escape = false; + + for c in input.chars() { + if escape { + current.push(c); + escape = false; + } else if c == '\\' { + escape = true; + } else if c == delimiter { + result.push(current.clone()); + current.clear(); + } else { + current.push(c); + } + } + + if !current.is_empty() { + result.push(current); + } + + result } fn get_str_key>(data: &Value, key: K) -> Option { @@ -249,30 +261,55 @@ fn get_str_key>(data: &Value, key: K) -> Option { match data { Value::Object(_) | Value::Array(_) | Value::String(_) => { // Exterior ref in case we need to make a new value in the match. - split_path(k).fold(Some(data.clone()), |acc, i| match acc? { - // If the current value is an object, try to get the value - Value::Object(map) => map.get(&i).map(Value::clone), - // If the current value is an array, we need an integer - // index. If integer conversion fails, return None. - Value::Array(arr) => i - .parse::() - .ok() - .and_then(|i| get(&arr, i)) - .map(Value::clone), - // Same deal if it's a string. - Value::String(s) => { - let s_chars: Vec = s.chars().collect(); - i.parse::() + split_with_escape(k, '.') + .into_iter() + .fold(Some(data.clone()), |acc, i| match acc? { + // If the current value is an object, try to get the value + Value::Object(map) => map.get(&i).map(Value::clone), + // If the current value is an array, we need an integer + // index. If integer conversion fails, return None. + Value::Array(arr) => i + .parse::() .ok() - .and_then(|i| get(&s_chars, i)) - .map(|c| c.to_string()) - .map(Value::String) - } - // This handles cases where we've got an un-indexable - // type or similar. - _ => None, - }) + .and_then(|i| get(&arr, i)) + .map(Value::clone), + // Same deal if it's a string. + Value::String(s) => { + let s_chars: Vec = s.chars().collect(); + i.parse::() + .ok() + .and_then(|i| get(&s_chars, i)) + .map(|c| c.to_string()) + .map(Value::String) + } + // This handles cases where we've got an un-indexable + // type or similar. + _ => None, + }) } _ => None, } } + +#[cfg(test)] +mod tests { + use super::*; + + // All the tests cases have been discussed here: https://github.com/Bestowinc/json-logic-rs/pull/37 + fn cases() -> Vec<(&'static str, Vec<&'static str>)> { + vec![ + (r#"foo.bar"#, vec!["foo", "bar"]), + (r#"foo\.bar"#, vec!["foo.bar"]), + (r#"foo\\.bar"#, vec!["foo\\", "bar"]), + (r#"foo\\\.bar"#, vec!["foo\\.bar"]), + (r#"foo\\bar"#, vec!["foo\\bar"]), + ] + } + + #[test] + fn test_split_with_escape() { + cases() + .into_iter() + .for_each(|(input, exp)| assert_eq!(split_with_escape(&input, '.'), exp)); + } +}