use super::types::PathSegment;
fn strip_key_quotes(key: &str) -> &str {
for delimiter in ['"', '\''] {
if key.len() >= 2 && key.starts_with(delimiter) && key.ends_with(delimiter) {
return &key[1..key.len() - 1];
}
}
key
}
fn is_numeric_key(key: &str) -> bool {
let unquoted = strip_key_quotes(key);
!unquoted.is_empty() && unquoted.chars().all(|c| c.is_ascii_digit())
}
pub(super) fn strip_numeric_indices(path: &str) -> String {
let mut result = String::with_capacity(path.len());
let mut chars = path.chars().peekable();
while let Some(c) = chars.next() {
if c == '[' {
let mut key = String::new();
let mut closed = false;
for inner in chars.by_ref() {
if inner == ']' {
closed = true;
break;
}
key.push(inner);
}
if closed && is_numeric_key(&key) {
} else {
result.push('[');
result.push_str(&key);
if closed {
result.push(']');
}
}
} else {
result.push(c);
}
}
while result.contains("..") {
result = result.replace("..", ".");
}
if result.starts_with('.') {
result.remove(0);
}
result
}
pub(crate) fn normalize_numeric_indices(path: &str) -> String {
let mut result = String::with_capacity(path.len());
let mut chars = path.chars().peekable();
while let Some(c) = chars.next() {
if c == '[' {
let mut key = String::new();
let mut closed = false;
for inner in chars.by_ref() {
if inner == ']' {
closed = true;
break;
}
key.push(inner);
}
if closed && is_numeric_key(&key) {
result.push_str("[0]");
} else {
result.push('[');
result.push_str(&key);
if closed {
result.push(']');
}
}
} else {
result.push(c);
}
}
result
}
pub(crate) fn normalize_indices_to_wildcards(path: &str) -> String {
let mut normalized = String::with_capacity(path.len());
let mut characters = path.chars().peekable();
while let Some(character) = characters.next() {
if character != '[' {
normalized.push(character);
continue;
}
let mut index = String::new();
while let Some(&inner) = characters.peek() {
characters.next();
if inner == ']' {
break;
}
index.push(inner);
}
normalized.push('[');
if !is_numeric_key(&index) {
normalized.push_str(&index);
}
normalized.push(']');
}
normalized
}
pub(super) fn segment_name(segment: &PathSegment) -> Option<&str> {
match segment {
PathSegment::Field(name) | PathSegment::ArrayField { name, .. } => Some(name),
PathSegment::MapAccess { field, .. } => Some(field),
PathSegment::Length => None,
}
}
pub(super) fn parse_path(path: &str) -> Vec<PathSegment> {
let mut segments = Vec::new();
for part in path.split('.') {
if part == "length" || part == "count" || part == "size" {
segments.push(PathSegment::Length);
} else if let Some(bracket_pos) = part.find('[') {
let name = part[..bracket_pos].to_string();
let key = strip_key_quotes(part[bracket_pos + 1..].trim_end_matches(']')).to_string();
if key.is_empty() {
segments.push(PathSegment::ArrayField { name, index: 0 });
} else if !key.is_empty() && key.chars().all(|c| c.is_ascii_digit()) {
let index: usize = key.parse().unwrap_or(0);
segments.push(PathSegment::ArrayField { name, index });
} else {
segments.push(PathSegment::MapAccess { field: name, key });
}
} else {
segments.push(PathSegment::Field(part.to_string()));
}
}
segments
}