Skip to main content

agent_first_data/document/
path.rs

1//! The single path grammar used by every traversal operation.
2//!
3//! A dot separates segments. `\.` embeds a dot in a key and `\\` embeds a
4//! backslash. Every other escape is rejected so a path is reversible.
5
6use crate::document::{DocumentError, DocumentResult};
7
8pub fn parse_path(path: &str) -> DocumentResult<Vec<String>> {
9    if path.is_empty() {
10        return Err(DocumentError::EmptyPath);
11    }
12    let mut segments = Vec::new();
13    let mut segment = String::new();
14    let mut escaped = false;
15    for character in path.chars() {
16        if escaped {
17            match character {
18                '.' | '\\' => segment.push(character),
19                other => {
20                    return Err(DocumentError::ParseError {
21                        format: "path".to_string(),
22                        detail: format!("invalid escape `\\{other}`"),
23                    });
24                }
25            }
26            escaped = false;
27        } else {
28            match character {
29                '\\' => escaped = true,
30                '.' => {
31                    if segment.is_empty() {
32                        return Err(DocumentError::ParseError {
33                            format: "path".to_string(),
34                            detail: "empty path segment".to_string(),
35                        });
36                    }
37                    segments.push(std::mem::take(&mut segment));
38                }
39                other => segment.push(other),
40            }
41        }
42    }
43    if escaped {
44        return Err(DocumentError::ParseError {
45            format: "path".to_string(),
46            detail: "trailing path escape".to_string(),
47        });
48    }
49    if segment.is_empty() {
50        return Err(DocumentError::ParseError {
51            format: "path".to_string(),
52            detail: "empty path segment".to_string(),
53        });
54    }
55    segments.push(segment);
56    Ok(segments)
57}
58
59pub fn join_path(segments: &[String]) -> String {
60    segments
61        .iter()
62        .map(|segment| segment.replace('\\', "\\\\").replace('.', "\\."))
63        .collect::<Vec<_>>()
64        .join(".")
65}