sjdiff 0.0.7

Structural JSON Diff Library for Rust
Documentation
use crate::{ArrayIndex, PathElement};

pub(crate) fn parse_element_path(s: &str) -> Result<Vec<PathElement>, String> {
    if s.is_empty() {
        return Err("Empty path is not allowed".to_string());
    }

    let mut result = Vec::new();
    let mut chars = s.chars().peekable();
    let mut current = String::new();
    let mut in_quotes = false;
    let mut in_brackets = false;

    while let Some(c) = chars.next() {
        match c {
            '\'' => {
                if in_quotes {
                    if current.is_empty() {
                        return Err("Empty quoted string is not allowed".to_string());
                    }
                    result.push(PathElement::Key(current.clone()));
                    current.clear();
                    in_quotes = false;
                } else {
                    if !current.is_empty() {
                        return Err("Unexpected quote".to_string());
                    }
                    in_quotes = true;
                }
            }
            '.' => {
                if in_quotes {
                    current.push(c);
                } else {
                    if !current.is_empty() {
                        result.push(PathElement::Key(current.clone()));
                        current.clear();
                    } else if result.is_empty() {
                        return Err("Path cannot start with a dot".to_string());
                    }
                }
            }
            '[' => {
                if in_quotes {
                    current.push(c);
                } else {
                    if !current.is_empty() {
                        result.push(PathElement::Key(current.clone()));
                        current.clear();
                    }
                    in_brackets = true;
                }
            }
            ']' => {
                if in_quotes {
                    current.push(c);
                } else if in_brackets {
                    if current == "_" {
                        result.push(PathElement::ArrayIndex(ArrayIndex::All));
                    } else {
                        match current.parse::<usize>() {
                            Ok(index) => result.push(PathElement::ArrayIndex(ArrayIndex::Index(index))),
                            Err(_) => return Err(format!("Invalid array index: {}", current)),
                        }
                    }
                    current.clear();
                    in_brackets = false;
                } else {
                    return Err("Unexpected closing bracket".to_string());
                }
            }
            _ => {
                current.push(c);
            }
        }
    }

    if in_quotes {
        return Err("Unclosed quote".to_string());
    }

    if in_brackets {
        return Err("Unclosed bracket".to_string());
    }

    if !current.is_empty() {
        result.push(PathElement::Key(current));
    }

    if result.is_empty() {
        return Err("Empty path is not allowed".to_string());
    }

    Ok(result)
}

#[cfg(test)]
mod tests {
    use crate::{ArrayIndex, PathElement};
    use super::*;

    #[test]
    fn test_parse_element_path() {
        assert_eq!(
            parse_element_path("a.b.c").unwrap(),
            vec![
                PathElement::Key("a".to_string()),
                PathElement::Key("b".to_string()),
                PathElement::Key("c".to_string())
            ]
        );

        assert_eq!(
            parse_element_path("[_].a.c").unwrap(),
            vec![
                PathElement::ArrayIndex(ArrayIndex::All),
                PathElement::Key("a".to_string()),
                PathElement::Key("c".to_string())
            ]
        );

        assert_eq!(
            parse_element_path("'[_]'.a").unwrap(),
            vec![
                PathElement::Key("[_]".to_string()),
                PathElement::Key("a".to_string())
            ]
        );

        assert_eq!(
            parse_element_path("a.[1]").unwrap(),
            vec![
                PathElement::Key("a".to_string()),
                PathElement::ArrayIndex(ArrayIndex::Index(1))
            ]
        );

        assert_eq!(
            parse_element_path("a.'.'.b").unwrap(),
            vec![
                PathElement::Key("a".to_string()),
                PathElement::Key(".".to_string()),
                PathElement::Key("b".to_string())
            ]
        );

        assert!(parse_element_path("").is_err());
        assert!(parse_element_path("''").is_err());
        assert!(parse_element_path("a.'").is_err());
        assert!(parse_element_path("a.[").is_err());
        assert!(parse_element_path("a.[x]").is_err());
    }
}