use std::collections::{BTreeSet, HashMap};
use crate::util::app_config_reader::AppConfigReader;
use crate::util::config_reader::ConfigReader;
use crate::util::multi_level_map::ConfigValue;
pub(crate) struct PreloadInfo {
pub routes: BTreeSet<String>,
pub instances: i64,
}
pub(crate) fn preload_override(config: &AppConfigReader) -> HashMap<String, PreloadInfo> {
let mut result: HashMap<String, PreloadInfo> = HashMap::new();
let Some(path) = config.get_property("yaml.preload.override") else {
return result;
};
for location in path.split(',').map(str::trim).filter(|p| !p.is_empty()) {
match parse_override_file(location) {
Ok(tasks) => merge_tasks(&mut result, tasks),
Err(message) => {
log::error!("Unable to load PreLoad entries from {location} - {message}");
}
}
}
result
}
fn merge_tasks(result: &mut HashMap<String, PreloadInfo>, tasks: Vec<(String, PreloadInfo)>) {
for (original, info) in tasks {
match result.get_mut(&original) {
Some(prior) => {
prior.routes.extend(info.routes);
if prior.instances == -1 {
prior.instances = info.instances;
}
}
None => {
result.insert(original, info);
}
}
}
}
fn parse_override_file(location: &str) -> Result<Vec<(String, PreloadInfo)>, String> {
let config = ConfigReader::load(location).map_err(|e| e.to_string())?;
let Some(ConfigValue::List(items)) = config.get("preload") else {
return Err("preload must be a list of key-values for original and routes".to_string());
};
let mut result = Vec::new();
for i in 0..items.len() {
if !matches!(
config.get(&format!("preload[{i}]")),
Some(ConfigValue::Map(_))
) {
return Err(format!("preload[{i}] is not a map of original and routes"));
}
let original = config
.get_property(&format!("preload[{i}].original"))
.unwrap_or_default();
if original.is_empty() {
return Err(format!("preload[{i}] does not contain 'original'"));
}
let Some(ConfigValue::List(route_items)) = config.get(&format!("preload[{i}].routes"))
else {
return Err(format!("preload[{i}].routes must be a list"));
};
let mut routes = BTreeSet::new();
for j in 0..route_items.len() {
if let Some(route) = config.get_property(&format!("preload[{i}].routes[{j}]")) {
routes.insert(route);
}
}
if config
.get_property(&format!("preload[{i}].keep-original"))
.as_deref()
== Some("true")
{
routes.insert(original.clone());
}
let instances = config
.get_property(&format!("preload[{i}].instances"))
.and_then(|value| value.trim().parse::<i64>().ok())
.unwrap_or(-1);
result.push((original, PreloadInfo { routes, instances }));
}
Ok(result)
}
pub(crate) fn apply(
overrides: &HashMap<String, PreloadInfo>,
declared_route: &str,
resolved_instances: usize,
) -> (Vec<String>, usize) {
let declared: Vec<String> = declared_route
.split(',')
.map(str::trim)
.filter(|r| !r.is_empty())
.map(str::to_string)
.collect();
let Some(info) = declared.iter().find_map(|route| overrides.get(route)) else {
return (declared, resolved_instances);
};
let routes: Vec<String> = info.routes.iter().cloned().collect();
let rendered = format!("[{}]", routes.join(", "));
if info.instances > 0 {
log::info!(
"Preload [{declared_route}] as {rendered}, instances {resolved_instances} to {}",
info.instances
);
(routes, info.instances as usize)
} else {
log::info!("Preload [{declared_route}] as {rendered}");
(routes, resolved_instances)
}
}