#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathSegment {
Field(String),
OptionalField(String),
ArrayIterator,
ArrayIndex(i64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedPath {
pub segments: Vec<PathSegment>,
pub partial: String,
}
impl ParsedPath {
pub fn new(segments: Vec<PathSegment>, partial: String) -> Self {
Self { segments, partial }
}
pub fn empty() -> Self {
Self {
segments: Vec::new(),
partial: String::new(),
}
}
}
pub fn parse_path(input: &str) -> ParsedPath {
if input.is_empty() {
return ParsedPath::empty();
}
let mut segments = Vec::new();
let mut chars = input.chars().peekable();
let mut partial = String::new();
while let Some(ch) = chars.next() {
match ch {
'.' => {
if !partial.is_empty() {
segments.push(field_segment(&partial, false));
partial.clear();
}
match chars.peek() {
Some('[') => {
chars.next();
if let Some(segment) = parse_bracket_content(&mut chars) {
segments.push(segment);
}
}
Some(&c) if is_field_start_char(c) => {
partial = collect_field_name(&mut chars);
}
Some('.') => {
}
Some(_) | None => {
}
}
}
'[' => {
if !partial.is_empty() {
segments.push(field_segment(&partial, false));
partial.clear();
}
if let Some(segment) = parse_bracket_content(&mut chars) {
segments.push(segment);
}
}
'?' => {
if !partial.is_empty() {
segments.push(field_segment(&partial, true));
partial.clear();
}
}
'(' => {
partial.clear();
skip_function_call(&mut chars);
}
c if is_field_char(c) => {
partial.push(c);
}
_ => {
break;
}
}
}
ParsedPath::new(segments, partial)
}
fn field_segment(name: &str, optional: bool) -> PathSegment {
if optional {
PathSegment::OptionalField(name.to_string())
} else {
PathSegment::Field(name.to_string())
}
}
fn is_field_start_char(c: char) -> bool {
c.is_alphabetic() || c == '_'
}
fn is_field_char(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}
fn collect_field_name(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
let mut name = String::new();
while let Some(&c) = chars.peek() {
if is_field_char(c) {
name.push(c);
chars.next();
} else {
break;
}
}
name
}
fn parse_bracket_content(chars: &mut std::iter::Peekable<std::str::Chars>) -> Option<PathSegment> {
match chars.peek() {
Some(']') => {
chars.next();
skip_optional_marker(chars);
Some(PathSegment::ArrayIterator)
}
Some('"') => {
chars.next();
let field_name = collect_quoted_string(chars);
skip_closing_bracket(chars);
Some(PathSegment::Field(field_name))
}
Some(&c) if c.is_ascii_digit() || c == '-' => {
let index = collect_number(chars);
skip_closing_bracket(chars);
Some(PathSegment::ArrayIndex(index))
}
_ => None,
}
}
fn collect_quoted_string(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
let mut result = String::new();
let mut escaped = false;
for c in chars.by_ref() {
if escaped {
result.push(c);
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '"' {
break;
} else {
result.push(c);
}
}
result
}
fn collect_number(chars: &mut std::iter::Peekable<std::str::Chars>) -> i64 {
let mut num_str = String::new();
while let Some(&c) = chars.peek() {
if c.is_ascii_digit() || c == '-' {
num_str.push(c);
chars.next();
} else {
break;
}
}
num_str.parse().unwrap_or(0)
}
fn skip_closing_bracket(chars: &mut std::iter::Peekable<std::str::Chars>) {
if chars.peek() == Some(&']') {
chars.next();
skip_optional_marker(chars);
}
}
fn skip_optional_marker(chars: &mut std::iter::Peekable<std::str::Chars>) {
if chars.peek() == Some(&'?') {
chars.next();
}
}
fn skip_function_call(chars: &mut std::iter::Peekable<std::str::Chars>) {
let mut depth = 1;
while depth > 0 {
match chars.next() {
Some('(') => depth += 1,
Some(')') => depth -= 1,
Some('"') => skip_string_in_function(chars),
Some(_) => {} None => break,
}
}
skip_optional_marker(chars);
}
fn skip_string_in_function(chars: &mut std::iter::Peekable<std::str::Chars>) {
let mut escaped = false;
for c in chars.by_ref() {
if escaped {
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '"' {
break;
}
}
}