use std::collections::{BTreeMap, BTreeSet};
use crate::ConfigError;
use crate::metadata::paths::{
alias_mapping_is_lossless, alias_overlap_sample_path, alias_patterns_are_ambiguous,
validate_metadata_path,
};
use super::super::{ConfigMetadata, FieldMetadata};
impl ConfigMetadata {
pub fn alias_overrides(&self) -> Result<BTreeMap<String, String>, ConfigError> {
let mut aliases = BTreeMap::<String, String>::new();
let canonical_paths = self
.fields
.iter()
.map(|field| field.path.clone())
.collect::<BTreeSet<_>>();
for field in &self.fields {
validate_metadata_path(&field.path)?;
for alias in &field.aliases {
validate_metadata_path(alias)?;
validate_alias(field, alias, &canonical_paths)?;
if let Some(first_path) = aliases.get(alias)
&& first_path != &field.path
{
return Err(ConfigError::MetadataConflict {
kind: "alias",
name: alias.clone(),
first_path: first_path.clone(),
second_path: field.path.clone(),
});
}
if let Some((other_alias, sample_path)) =
ambiguous_alias_overlap(&aliases, alias, &field.path)
{
return Err(ConfigError::MetadataInvalid {
path: alias.clone(),
message: format!(
"alias `{alias}` overlaps ambiguously with `{other_alias}` for concrete path `{sample_path}`"
),
});
}
aliases.insert(alias.clone(), field.path.clone());
}
}
Ok(aliases)
}
}
fn validate_alias(
field: &FieldMetadata,
alias: &str,
canonical_paths: &BTreeSet<String>,
) -> Result<(), ConfigError> {
if alias.is_empty() {
return Err(ConfigError::MetadataInvalid {
path: alias.to_owned(),
message: "aliases cannot target the root path".to_owned(),
});
}
if field.path.is_empty() {
return Err(ConfigError::MetadataInvalid {
path: alias.to_owned(),
message: "aliases cannot rewrite the root path".to_owned(),
});
}
if !alias_mapping_is_lossless(alias, &field.path) {
return Err(ConfigError::MetadataInvalid {
path: alias.to_owned(),
message: format!(
"alias `{alias}` must preserve wildcard positions and cannot be deeper than canonical path `{}`",
field.path
),
});
}
if canonical_paths.contains(alias) && alias != field.path {
return Err(ConfigError::MetadataConflict {
kind: "alias",
name: alias.to_owned(),
first_path: alias.to_owned(),
second_path: field.path.clone(),
});
}
Ok(())
}
fn ambiguous_alias_overlap(
aliases: &BTreeMap<String, String>,
alias: &str,
canonical: &str,
) -> Option<(String, String)> {
aliases.iter().find_map(|(other_alias, other_canonical)| {
alias_patterns_are_ambiguous(alias, canonical, other_alias, other_canonical).then(|| {
(
other_alias.clone(),
alias_overlap_sample_path(alias, other_alias),
)
})
})
}