crate::ix!();
pub fn get_setting(
settings: &Settings,
section: &str,
name: &str,
ignore_default_section_config: bool,
get_chain_name: bool,
) -> SettingsValue {
debug!(
"get_setting: section='{section}', name='{name}', ignore_default_section_config={ignore_default_section_config}, get_chain_name={get_chain_name}"
);
let mut result: Option<SettingsValue> = None;
let mut done = false;
let section_s = section.to_owned();
let name_s = name.to_owned();
merge_settings(settings, §ion_s, &name_s, |span: SettingsSpan, source: Source| {
let source_str = match source {
Source::FORCED => "FORCED",
Source::COMMAND_LINE => "COMMAND_LINE",
Source::RW_SETTINGS => "RW_SETTINGS",
Source::CONFIG_FILE_NETWORK_SECTION => "CONFIG_FILE_NETWORK_SECTION",
Source::CONFIG_FILE_DEFAULT_SECTION => "CONFIG_FILE_DEFAULT_SECTION",
};
let never_ignore_negated_setting = span.last_negated();
let reverse_precedence =
matches!(source, Source::CONFIG_FILE_NETWORK_SECTION | Source::CONFIG_FILE_DEFAULT_SECTION)
&& !get_chain_name;
let skip_negated_command_line = get_chain_name;
if done {
trace!("get_setting: already decided (done=true), skipping source={source_str}");
return;
}
if ignore_default_section_config
&& matches!(source, Source::CONFIG_FILE_DEFAULT_SECTION)
&& !never_ignore_negated_setting
{
debug!("get_setting: ignoring default-section values for name='{name}' (except final negation) from source={source_str}");
return;
}
if skip_negated_command_line && span.last_negated() {
debug!("get_setting: skipping negated command-line value for name='{name}'");
return;
}
if !span.empty() {
let chosen = unsafe {
if reverse_precedence {
(*span.begin()).clone()
} else {
(*span.end().offset(-1)).clone()
}
};
trace!(
"get_setting: selecting value (reverse_precedence={reverse_precedence}) from source={source_str}: {}",
chosen
);
result = Some(chosen);
done = true;
} else if span.last_negated() {
trace!(
"get_setting: span empty but last value is explicit negation; returning false (source={source_str})"
);
result = Some(SettingsValue::from(false));
done = true;
} else {
trace!(
"get_setting: span empty and not negated for source={source_str}; continuing"
);
}
});
let out = result.unwrap_or_else(|| {
debug!("get_setting: no value found; returning null");
SettingsValue(UniValue::null())
});
info!("get_setting: section='{section}', name='{name}' -> {}", out);
out
}
#[cfg(test)]
mod get_setting_behavior_spec {
use super::*;
fn build_settings(
forced: HashMap<String, SettingsValue>,
cli: HashMap<String, Vec<SettingsValue>>,
rw: HashMap<String, SettingsValue>,
ro: HashMap<String, HashMap<String, Vec<SettingsValue>>>,
) -> Settings {
SettingsBuilder::default()
.forced_settings(forced)
.command_line_options(cli)
.rw_settings(rw)
.ro_config(ro)
.build()
.expect("settings builder")
}
#[traced_test]
fn ensure_forced_value_wins_get_setting() {
info!("Verifying that a FORCED value wins and short-circuits further merging");
let mut forced = HashMap::new();
forced.insert("opt".into(), sv_json("\"forced\""));
let mut cli = HashMap::new();
cli.insert("opt".into(), vec![sv_json("\"cmd1\"")]);
let mut net_map = HashMap::new();
net_map.insert("opt".into(), vec![sv_json("\"confnet\"")]);
let mut ro = HashMap::new();
ro.insert("main".into(), net_map);
let settings = build_settings(forced, cli, HashMap::new(), ro);
let out = get_setting(&settings, "main", "opt", false, false);
debug!("Result: {}", out);
assert_eq!(out, sv_json("\"forced\""));
}
#[traced_test]
fn respect_reverse_precedence_in_config_sections_get_setting() {
info!("Verifying reverse precedence in config file sections when not querying chain name");
let mut net_map = HashMap::new();
net_map.insert(
"alpha".into(),
vec![sv_json("\"first\""), sv_json("\"second\"")],
);
let mut ro = HashMap::new();
ro.insert("main".into(), net_map);
let settings = build_settings(HashMap::new(), HashMap::new(), HashMap::new(), ro);
let out = get_setting(&settings, "main", "alpha", false, false);
debug!("Result: {}", out);
assert_eq!(out, sv_json("\"first\""), "reverse precedence should pick first");
}
#[traced_test]
fn skip_negated_command_line_for_chain_name_get_setting() {
info!("Verifying that negated CLI settings are skipped when get_chain_name=true");
let mut cli = HashMap::new();
cli.insert("chain".into(), vec![SettingsValue::from(false)]);
let mut net_map = HashMap::new();
net_map.insert("chain".into(), vec![sv_json("\"netvalue\"")]);
let mut ro = HashMap::new();
ro.insert("main".into(), net_map);
let settings = build_settings(HashMap::new(), cli, HashMap::new(), ro);
let out = get_setting(&settings, "main", "chain", false, true);
debug!("Result: {}", out);
assert_eq!(out, sv_json("\"netvalue\""), "negated CLI must be skipped for chain name");
}
#[traced_test]
fn ignore_default_section_unless_negated_get_setting() {
info!("Verifying ignoring non-negated default-section values when requested");
let mut default_map = HashMap::new();
default_map.insert("key".into(), vec![sv_json("\"dval\"")]);
let mut ro = HashMap::new();
ro.insert("".into(), default_map);
let settings = build_settings(HashMap::new(), HashMap::new(), HashMap::new(), ro);
let out = get_setting(&settings, "main", "key", true, false);
debug!("Result (non-negated default ignored): {}", out);
assert_eq!(out, SettingsValue(UniValue::null()));
info!("Verifying that final negation in default section still applies even if ignored flag is set");
let mut default_map2 = HashMap::new();
default_map2.insert("flag".into(), vec![SettingsValue::from(false)]);
let mut ro2 = HashMap::new();
ro2.insert("".into(), default_map2);
let settings2 = build_settings(HashMap::new(), HashMap::new(), HashMap::new(), ro2);
let out2 = get_setting(&settings2, "regtest", "flag", true, false);
debug!("Result (negated default applied): {}", out2);
assert_eq!(out2, SettingsValue::from(false));
}
#[traced_test]
fn negated_only_result_is_false_get_setting() {
info!("Verifying that a lone negation yields boolean false");
let mut cli = HashMap::new();
cli.insert("feature".into(), vec![SettingsValue::from(false)]);
let settings = build_settings(HashMap::new(), cli, HashMap::new(), HashMap::new());
let out = get_setting(&settings, "main", "feature", false, false);
debug!("Result: {}", out);
assert_eq!(out, SettingsValue::from(false));
}
#[traced_test]
fn choose_last_value_for_non_config_sources_get_setting() {
info!("Verifying that for non-config sources, last assignment wins");
let mut cli = HashMap::new();
cli.insert(
"opt".into(),
vec![sv_json("\"first\""), sv_json("\"second\"")],
);
let settings = build_settings(HashMap::new(), cli, HashMap::new(), HashMap::new());
let out = get_setting(&settings, "main", "opt", false, false);
debug!("Result: {}", out);
assert_eq!(out, sv_json("\"second\""));
}
}
#[cfg(test)]
mod get_setting_precedence_additional_spec {
use super::*;
fn sv_json(j: &str) -> SettingsValue {
SettingsValue(UniValue::from(j))
}
fn build_settings(
forced: HashMap<String, SettingsValue>,
cli: HashMap<String, Vec<SettingsValue>>,
rw: HashMap<String, SettingsValue>,
ro: HashMap<String, HashMap<String, Vec<SettingsValue>>>,
) -> Settings {
SettingsBuilder::default()
.forced_settings(forced)
.command_line_options(cli)
.rw_settings(rw)
.ro_config(ro)
.build()
.expect("settings builder")
}
#[traced_test]
fn cli_overrides_rw_and_all_config_sources_get_setting() {
info!("Ensuring command-line takes precedence over RW and any config sections");
let mut cli = HashMap::new();
cli.insert("k".into(), vec![sv_json("\"cli1\""), sv_json("\"cli2\"")]);
let mut rw = HashMap::new();
rw.insert("k".into(), sv_json("\"rw\""));
let mut net_map = HashMap::new();
net_map.insert("k".into(), vec![sv_json("\"net\"")]);
let mut def_map = HashMap::new();
def_map.insert("k".into(), vec![sv_json("\"def\"")]);
let mut ro = HashMap::new();
ro.insert("main".into(), net_map);
ro.insert("".into(), def_map);
let settings = build_settings(HashMap::new(), cli, rw, ro);
let out = get_setting(&settings, "main", "k", false, false);
debug!("Result: {}", out);
assert_eq!(out, sv_json("\"cli2\""), "last CLI value should win");
}
#[traced_test]
fn get_chain_name_negated_then_nonnegated_cli_is_respected() {
info!("When get_chain_name=true and CLI ends non-negated, the CLI value is used");
let mut cli = HashMap::new();
cli.insert(
"chain".into(),
vec![SettingsValue::from(false), sv_json("\"cli\"")],
);
let settings = build_settings(HashMap::new(), cli, HashMap::new(), HashMap::new());
let out = get_setting(&settings, "main", "chain", false, true);
debug!("Result: {}", out);
assert_eq!(out, sv_json("\"cli\""));
}
}