use crate::util::app_config_reader::AppConfigReader;
pub fn is_required(condition: Option<&str>, config: &AppConfigReader) -> bool {
is_required_with(condition, |key| config.get_property(key))
}
fn is_required_with(condition: Option<&str>, lookup: impl Fn(&str) -> Option<String>) -> bool {
let Some(value) = condition else {
return true;
};
for term in value.split(',') {
let statement = term.trim();
let matched = match statement.strip_prefix('!') {
Some(rest) => !evaluate(rest, &lookup),
None => evaluate(statement, &lookup),
};
if matched {
return true;
}
}
false
}
fn evaluate(statement: &str, lookup: &impl Fn(&str) -> Option<String>) -> bool {
let condition = statement.trim();
if let Some(eq) = condition.find('=') {
let key = condition[..eq].trim();
let value = condition[eq + 1..].trim();
if key.is_empty() {
return false;
}
let want = if value.is_empty() { "true" } else { value };
lookup(key).is_some_and(|actual| actual.eq_ignore_ascii_case(want))
} else {
lookup(condition).is_some_and(|actual| actual.eq_ignore_ascii_case("true"))
}
}
#[cfg(test)]
mod tests {
use super::is_required_with;
use std::collections::HashMap;
fn config(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let map: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
move |key: &str| map.get(key).cloned()
}
#[test]
fn no_condition_is_always_required() {
assert!(is_required_with(None, config(&[])));
}
#[test]
fn key_equals_value() {
let cfg = config(&[("app.env", "dev")]);
assert!(is_required_with(Some("app.env=dev"), &cfg));
assert!(!is_required_with(Some("app.env=prod"), &cfg));
assert!(!is_required_with(Some("missing.key=dev"), &cfg));
}
#[test]
fn value_match_is_case_insensitive() {
let cfg = config(&[("app.env", "Dev")]);
assert!(is_required_with(Some("app.env=dev"), &cfg));
}
#[test]
fn bare_key_and_empty_value_mean_true() {
let cfg = config(&[("feature.on", "true"), ("feature.off", "false")]);
assert!(is_required_with(Some("feature.on"), &cfg));
assert!(is_required_with(Some("feature.on="), &cfg));
assert!(!is_required_with(Some("feature.off"), &cfg));
assert!(!is_required_with(Some("missing"), &cfg));
}
#[test]
fn negation() {
let cfg = config(&[("kill.switch", "true")]);
assert!(!is_required_with(Some("!kill.switch"), &cfg));
assert!(is_required_with(Some("!other.switch"), &cfg)); }
#[test]
fn comma_separated_is_or() {
let cfg = config(&[("key.two", "200")]);
assert!(is_required_with(Some("key.one=100,key.two=200"), &cfg));
assert!(!is_required_with(Some("key.one=100,key.two=999"), &cfg));
}
#[test]
fn empty_key_is_false() {
assert!(!is_required_with(Some("=dev"), config(&[])));
}
}