use std::collections::BTreeMap;
use crate::path::{concrete_paths_overlap, get_value_at_path};
use crate::{ConfigError, ConfigMetadata, EnvDecoder};
mod context;
mod insert;
use super::EnvSource;
use super::name::path_for_env_var;
use super::state::EnvLayerState;
use super::target::{
canonicalize_runtime_env_target_path, validate_binding_names,
validate_binding_override_conflicts, validate_binding_paths,
};
use crate::loader::{CustomEnvDecoder, Layer};
use self::context::EnvLayerContext;
use self::insert::insert_env_value;
impl EnvSource {
pub(in crate::loader) fn into_layer(
self,
metadata: &ConfigMetadata,
env_decoders: &BTreeMap<String, EnvDecoder>,
custom_env_decoders: &BTreeMap<String, CustomEnvDecoder>,
runtime_layers: &[Layer],
) -> Result<Option<Layer>, ConfigError> {
let EnvSource {
vars,
var_conflicts,
prefix,
separator,
lowercase_segments,
bindings,
binding_conflicts,
} = self;
if let Some(conflict) = var_conflicts.into_iter().next() {
return Err(ConfigError::InvalidEnv {
name: conflict.name,
path: String::new(),
message: "environment source contains duplicate variable names".to_owned(),
});
}
validate_binding_names(&bindings)?;
if let Some(conflict) = binding_conflicts.into_iter().next() {
let path = conflict.second.path.clone();
return Err(ConfigError::InvalidEnv {
name: conflict.name,
path,
message: format!(
"conflicting explicit env bindings target `{}` and `{}`",
conflict.first.path, conflict.second.path
),
});
}
let alias_overrides = metadata.alias_overrides()?;
validate_binding_paths(&bindings, &alias_overrides)?;
let env_overrides = metadata.env_overrides()?;
let context = EnvLayerContext {
metadata,
alias_overrides: &alias_overrides,
env_decoders,
custom_env_decoders,
runtime_layers,
};
validate_binding_override_conflicts(
&bindings,
&env_overrides,
&alias_overrides,
runtime_layers,
)?;
let mut state = EnvLayerState::new();
let mut fallback_vars = Vec::new();
for (name, raw_value) in vars {
if let Some(binding) = bindings.get(&name) {
if binding.fallback {
fallback_vars.push((name, raw_value, binding.clone()));
} else {
insert_env_value(
&context,
&mut state,
&name,
&raw_value,
&binding.path,
Some(binding.path.clone()),
binding.decoder,
)?;
}
continue;
}
if let Some(path) = env_overrides.get(&name) {
insert_env_value(
&context,
&mut state,
&name,
&raw_value,
path,
Some(path.clone()),
None,
)?;
continue;
}
let Some(path) =
path_for_env_var(&name, prefix.as_deref(), &separator, lowercase_segments)
.map_err(|error| ConfigError::InvalidEnv {
name: name.clone(),
path: error.path,
message: error.message,
})?
else {
continue;
};
insert_env_value(
&context,
&mut state,
&name,
&raw_value,
&path,
Some(path.clone()),
None,
)?;
}
let mut claimed_fallback_paths = BTreeMap::new();
for (name, raw_value, binding) in fallback_vars {
let normalized = canonicalize_runtime_env_target_path(
&name,
&binding.path,
&alias_overrides,
runtime_layers,
&state.root,
)?;
if normalized.is_empty() {
continue;
}
reject_conflicting_fallback(&name, &normalized, &claimed_fallback_paths)?;
if get_value_at_path(&state.root, &normalized).is_some() {
continue;
}
claimed_fallback_paths.insert(normalized.clone(), name.clone());
insert_env_value(
&context,
&mut state,
&name,
&raw_value,
&binding.path,
Some(binding.path.clone()),
binding.decoder,
)?;
}
Ok(state.into_layer())
}
}
fn reject_conflicting_fallback(
name: &str,
path: &str,
claimed_fallback_paths: &BTreeMap<String, String>,
) -> Result<(), ConfigError> {
for (existing_path, existing_name) in claimed_fallback_paths {
if concrete_paths_overlap(existing_path, path) {
return Err(ConfigError::InvalidEnv {
name: name.to_owned(),
path: path.to_owned(),
message: format!(
"conflicting fallback environment variables `{existing_name}` and `{name}` target overlapping configuration paths `{existing_path}` and `{path}`"
),
});
}
}
Ok(())
}