use std::collections::BTreeMap;
use serde_json::{Map, Value};
use crate::path::{canonicalize_path_with_aliases, join_path};
use crate::{ConfigError, ConfigMetadata};
use super::super::de::insert_path;
use super::super::merge::ensure_root_object;
use super::super::path::ensure_path_safe_keys;
pub(in crate::loader) fn canonicalize_value_paths(
value: &Value,
metadata: &ConfigMetadata,
) -> Result<Value, ConfigError> {
ensure_root_object(value)?;
ensure_path_safe_keys(value, "")?;
let aliases = metadata.alias_overrides()?;
canonicalize_value_paths_with_aliases(value, &aliases)
}
pub(in crate::loader) fn canonicalize_value_paths_with_aliases(
value: &Value,
aliases: &BTreeMap<String, String>,
) -> Result<Value, ConfigError> {
ensure_root_object(value)?;
ensure_path_safe_keys(value, "")?;
if aliases.is_empty() {
return Ok(value.clone());
}
let mut canonical = Value::Object(Map::new());
let mut nodes = Vec::new();
collect_value_nodes(value, "", &mut nodes);
let mut seen = BTreeMap::<String, String>::new();
for (path, node) in nodes {
let canonical_path = canonicalize_path_with_aliases(&path, aliases);
if let Some(first_path) = seen.get(&canonical_path)
&& first_path != &path
{
return Err(ConfigError::PathConflict {
first_path: first_path.clone(),
second_path: path,
canonical_path,
});
}
seen.insert(canonical_path.clone(), path);
let segments = canonical_path.split('.').collect::<Vec<_>>();
insert_path(&mut canonical, &segments, node).map_err(|message| {
ConfigError::InvalidArg {
arg: canonical_path.clone(),
message,
}
})?;
}
Ok(canonical)
}
fn collect_value_nodes(value: &Value, current: &str, nodes: &mut Vec<(String, Value)>) {
match value {
Value::Object(map) if map.is_empty() && !current.is_empty() => {
nodes.push((current.to_owned(), Value::Object(Map::new())));
}
Value::Object(map) => {
for (key, child) in map {
let next = join_path(current, key);
collect_value_nodes(child, &next, nodes);
}
}
Value::Array(values) if values.is_empty() && !current.is_empty() => {
nodes.push((current.to_owned(), Value::Array(Vec::new())));
}
Value::Array(values) => {
for (index, child) in values.iter().enumerate() {
let next = join_path(current, &index.to_string());
collect_value_nodes(child, &next, nodes);
}
}
_ if !current.is_empty() => nodes.push((current.to_owned(), value.clone())),
_ => {}
}
}