const ROOT: &str = "DATA";
pub struct PathBuilder;
impl PathBuilder {
pub fn build(segments: &[&str]) -> String {
if segments.is_empty() {
return ROOT.to_string();
}
let mut result = String::from(ROOT);
for segment in segments {
if segment.is_empty() {
continue;
}
if segment.starts_with('[') && segment.ends_with(']') {
result.push_str(segment);
} else {
result.push('.');
result.push_str(segment);
}
}
result
}
pub fn build_from(base_path: &str, segments: &[&str]) -> String {
if base_path.is_empty() {
return Self::build(segments);
}
if segments.is_empty() {
return base_path.to_string();
}
let mut result = String::from(base_path);
for segment in segments {
if segment.is_empty() {
continue;
}
if segment.starts_with('[') && segment.ends_with(']') {
result.push_str(segment);
} else {
result.push('.');
result.push_str(segment);
}
}
result
}
pub fn build_from_scope(current_path: &str, property: &str) -> String {
if current_path == ROOT {
Self::build(&[property])
} else {
Self::build_from(current_path, &[property])
}
}
pub fn build_array_item(array_path: &str, index: usize) -> String {
format!("{}[{}]", array_path, index)
}
pub fn build_array_item_property(array_path: &str, index: usize, property: &str) -> String {
Self::build_from(&Self::build_array_item(array_path, index), &[property])
}
pub fn strip_root(path: &str) -> String {
if path.starts_with(ROOT) {
if path.len() == ROOT.len() {
String::new()
} else if path.chars().nth(ROOT.len()) == Some('.') {
path[ROOT.len() + 1..].to_string()
} else {
path.to_string()
}
} else {
path.to_string()
}
}
pub fn ensure_root(path: &str) -> String {
if path.is_empty() {
ROOT.to_string()
} else if path.starts_with(ROOT) {
path.to_string()
} else {
Self::build(&[path])
}
}
pub fn get_last_segment(path: &str) -> String {
if path.is_empty() {
return String::new();
}
if let Some(last_bracket) = path.rfind('[') {
if path.ends_with(']') {
return path[last_bracket..].to_string();
}
}
if let Some(last_dot) = path.rfind('.') {
path[last_dot + 1..].to_string()
} else {
path.to_string()
}
}
pub fn get_parent(path: &str) -> String {
if path.is_empty() || path == ROOT {
return ROOT.to_string();
}
if let Some(last_dot) = path.rfind('.') {
if last_dot <= ROOT.len() {
ROOT.to_string()
} else {
path[..last_dot].to_string()
}
} else {
ROOT.to_string()
}
}
pub fn get_segments(path: &str) -> Vec<String> {
let stripped = Self::strip_root(path);
if stripped.is_empty() {
return Vec::new();
}
let mut segments = Vec::new();
let mut current = String::new();
let chars: Vec<char> = stripped.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if c == '.' {
if !current.is_empty() {
segments.push(current.clone());
current.clear();
}
} else if c == '[' {
if !current.is_empty() {
segments.push(current.clone());
current.clear();
}
let mut bracket_content = String::from("[");
i += 1;
while i < chars.len() && chars[i] != ']' {
bracket_content.push(chars[i]);
i += 1;
}
if i < chars.len() {
bracket_content.push(']');
}
segments.push(bracket_content);
} else {
current.push(c);
}
i += 1;
}
if !current.is_empty() {
segments.push(current);
}
segments
}
pub fn is_array_index(path: &str) -> bool {
path.contains('[') && path.ends_with(']')
}
pub fn get_table_path(property_path: &str) -> String {
let segments = Self::get_segments(property_path);
if segments.len() <= 1 {
return ROOT.to_string();
}
let table_segments: Vec<&str> = segments[..segments.len() - 1]
.iter()
.map(|s| s.as_str())
.collect();
Self::build(&table_segments)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_simple() {
assert_eq!(PathBuilder::build(&["server", "host"]), "DATA.server.host");
}
#[test]
fn test_build_with_array() {
assert_eq!(PathBuilder::build(&["items", "[0]"]), "DATA.items[0]");
assert_eq!(PathBuilder::build(&["items", "[0]", "name"]), "DATA.items[0].name");
}
#[test]
fn test_build_from() {
assert_eq!(
PathBuilder::build_from("DATA.server", &["config", "host"]),
"DATA.server.config.host"
);
}
#[test]
fn test_strip_root() {
assert_eq!(PathBuilder::strip_root("DATA.server.host"), "server.host");
assert_eq!(PathBuilder::strip_root("DATA"), "");
}
#[test]
fn test_ensure_root() {
assert_eq!(PathBuilder::ensure_root("server.host"), "DATA.server.host");
assert_eq!(PathBuilder::ensure_root("DATA.server.host"), "DATA.server.host");
}
#[test]
fn test_get_last_segment() {
assert_eq!(PathBuilder::get_last_segment("DATA.server.config.host"), "host");
assert_eq!(PathBuilder::get_last_segment("DATA.items[0]"), "[0]");
}
#[test]
fn test_get_parent() {
assert_eq!(PathBuilder::get_parent("DATA.server.config.host"), "DATA.server.config");
assert_eq!(PathBuilder::get_parent("DATA.items[0].name"), "DATA.items[0]");
assert_eq!(PathBuilder::get_parent("DATA"), "DATA");
}
#[test]
fn test_get_segments() {
assert_eq!(
PathBuilder::get_segments("DATA.server.config.host"),
vec!["server", "config", "host"]
);
assert_eq!(
PathBuilder::get_segments("DATA.items[0].name"),
vec!["items", "[0]", "name"]
);
}
#[test]
fn test_is_array_index() {
assert!(PathBuilder::is_array_index("DATA.items[0]"));
assert!(!PathBuilder::is_array_index("DATA.items"));
}
#[test]
fn test_get_table_path() {
assert_eq!(
PathBuilder::get_table_path("DATA.server.config.host"),
"DATA.server.config"
);
assert_eq!(
PathBuilder::get_table_path("DATA.simple"),
"DATA"
);
}
}