use std::collections::BTreeMap;
use serde_json::Value;
use crate::ConfigError;
use crate::path::{canonicalize_path_with_aliases, concrete_paths_overlap};
use super::super::Layer;
use super::super::canonical::{canonicalize_runtime_path, canonicalize_runtime_path_across_layers};
use super::super::path::{invalid_concrete_path_segment, try_normalize_external_path};
use super::binding::EnvBinding;
pub(super) fn validate_binding_names(
bindings: &BTreeMap<String, EnvBinding>,
) -> Result<(), ConfigError> {
for (name, binding) in bindings {
if name.is_empty() {
return Err(ConfigError::InvalidEnv {
name: name.clone(),
path: binding.path.clone(),
message: "environment variable names cannot be empty".to_owned(),
});
}
}
Ok(())
}
pub(super) fn validate_binding_paths(
bindings: &BTreeMap<String, EnvBinding>,
alias_overrides: &BTreeMap<String, String>,
) -> Result<(), ConfigError> {
for (name, binding) in bindings {
canonical_env_target_path(name, &binding.path, alias_overrides)?;
}
Ok(())
}
pub(super) fn validate_binding_override_conflicts(
bindings: &BTreeMap<String, EnvBinding>,
env_overrides: &BTreeMap<String, String>,
alias_overrides: &BTreeMap<String, String>,
runtime_layers: &[Layer],
) -> Result<(), ConfigError> {
for (name, binding) in bindings {
let Some(metadata_path) = env_overrides.get(name) else {
continue;
};
let binding_path = canonical_env_target_path(name, &binding.path, alias_overrides)?;
let binding_path = canonicalize_runtime_path_across_layers(&binding_path, runtime_layers);
let metadata_path = canonicalize_runtime_path_across_layers(metadata_path, runtime_layers);
if binding_path != metadata_path {
return Err(ConfigError::InvalidEnv {
name: name.clone(),
path: binding.path.clone(),
message: format!(
"conflicting environment bindings target `{}` via EnvSource and `{}` via metadata",
binding_path, metadata_path
),
});
}
}
Ok(())
}
pub(super) fn canonicalize_runtime_env_target_path(
name: &str,
path: &str,
alias_overrides: &BTreeMap<String, String>,
runtime_layers: &[Layer],
current_root: &Value,
) -> Result<String, ConfigError> {
let path = canonical_env_target_path(name, path, alias_overrides)?;
let path = canonicalize_runtime_path_across_layers(&path, runtime_layers);
Ok(canonicalize_runtime_path(current_root, &path))
}
pub(super) fn claim_env_path(
name: &str,
path: &str,
claimed_paths: &mut BTreeMap<String, String>,
) -> Result<(), ConfigError> {
for (existing_path, existing_name) in claimed_paths.iter() {
if existing_name == name {
continue;
}
if existing_path == path {
return Err(ConfigError::InvalidEnv {
name: name.to_owned(),
path: path.to_owned(),
message: format!(
"conflicting environment variables `{existing_name}` and `{name}` both target `{path}`"
),
});
}
if concrete_paths_overlap(existing_path, path) {
return Err(ConfigError::InvalidEnv {
name: name.to_owned(),
path: path.to_owned(),
message: format!(
"conflicting environment variables `{existing_name}` and `{name}` target overlapping configuration paths `{existing_path}` and `{path}`"
),
});
}
}
claimed_paths.insert(path.to_owned(), name.to_owned());
Ok(())
}
fn canonical_env_target_path(
name: &str,
path: &str,
alias_overrides: &BTreeMap<String, String>,
) -> Result<String, ConfigError> {
let normalized =
try_normalize_external_path(path).map_err(|message| ConfigError::InvalidEnv {
name: name.to_owned(),
path: path.to_owned(),
message,
})?;
if normalized.is_empty() {
return Err(ConfigError::InvalidEnv {
name: name.to_owned(),
path: path.to_owned(),
message: "environment binding path cannot be empty".to_owned(),
});
}
if let Some((segment, message)) = invalid_concrete_path_segment(&normalized) {
return Err(ConfigError::InvalidEnv {
name: name.to_owned(),
path: path.to_owned(),
message: format!("environment binding path segment `{segment}` is invalid: {message}"),
});
}
Ok(canonicalize_path_with_aliases(&normalized, alias_overrides))
}