Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions crates/apollo_config/src/dumping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,38 @@ pub fn generate_struct_pointer<T: SerializeConfig>(
res
}

/// Generates pointers for an optional struct:
/// - All fields under `target_prefix` (using `default_instance` if provided, otherwise
/// `T::default()`), pointed to by each prefix in `pointer_prefixes`.
/// - The optional flag `<target_prefix>.#is_none`, pointed to by `<prefix>.#is_none` for each
/// prefix in `pointer_prefixes`.
pub fn generate_optional_struct_pointer<T: SerializeConfig + Default>(
target_prefix: ParamPath,
default_instance: Option<&T>,
pointer_prefixes: HashSet<ParamPath>,
is_none_default: bool,
) -> ConfigPointers {
// Use provided instance if given; otherwise use a local default that lives for this call.
let default_instance_value = match default_instance {
Some(instance) => instance,
None => &T::default(),
};
let mut res = generate_struct_pointer(
target_prefix.clone(),
default_instance_value,
pointer_prefixes.clone(),
);

let pointer_target = ser_is_param_none(target_prefix.as_str(), is_none_default);
let pointing_params: Pointers = pointer_prefixes
.into_iter()
.map(|prefix| format!("{prefix}{FIELD_SEPARATOR}{IS_NONE_MARK}"))
.collect();
res.push((pointer_target, pointing_params));

res
}

// Converts a serialized param to a pointer target.
fn serialized_param_to_pointer_target(
target_prefix: ParamPath,
Expand Down
60 changes: 55 additions & 5 deletions crates/apollo_config/src/loading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,64 @@ pub(crate) fn update_config_map_by_pointers(
config_map: &mut BTreeMap<ParamPath, Value>,
pointers_map: &BTreeMap<ParamPath, ParamPath>,
) -> Result<(), ConfigError> {
let optional_param_suffix = format!("{FIELD_SEPARATOR}{IS_NONE_MARK}");

// Phase 1: Resolve only the optional flags and compute disabled prefixes.
let mut disabled_prefixes: HashSet<String> = HashSet::new();
for (param_path, target_param_path) in pointers_map {
if param_path.ends_with(optional_param_suffix.as_str()) {
let target_value = match config_map.get(target_param_path) {
Some(v) => v.clone(),
None => {
return Err(ConfigError::PointerTargetNotFound {
target_param: target_param_path.to_owned(),
});
}
};
// Write the flag value
config_map.insert(param_path.to_owned(), target_value.clone());
// Record disabled prefixes where the flag is true
if target_value == json!(true) {
if let Some(prefix) = param_path.strip_suffix(optional_param_suffix.as_str()) {
disabled_prefixes.insert(prefix.to_owned());
}
}
}
}

// Phase 2: Resolve non-flag pointers, skipping any under disabled prefixes.
for (param_path, target_param_path) in pointers_map {
let Some(target_value) = config_map.get(target_param_path) else {
return Err(ConfigError::PointerTargetNotFound {
target_param: target_param_path.to_owned(),
});
if param_path.ends_with(optional_param_suffix.as_str()) {
continue;
}
// Check if param_path is under any disabled prefix by walking its ancestors
let mut is_under_disabled_prefix = false;
let mut prefix = String::new();
for part in param_path.split(FIELD_SEPARATOR) {
if !prefix.is_empty() {
prefix.push_str(FIELD_SEPARATOR);
}
prefix.push_str(part);
if disabled_prefixes.contains(&prefix) {
is_under_disabled_prefix = true;
break;
}
}
if is_under_disabled_prefix {
continue;
}

let target_value = match config_map.get(target_param_path) {
Some(v) => v.clone(),
None => {
return Err(ConfigError::PointerTargetNotFound {
target_param: target_param_path.to_owned(),
});
}
};
config_map.insert(param_path.to_owned(), target_value.clone());
config_map.insert(param_path.to_owned(), target_value);
}

Ok(())
}

Expand Down