use figment::{Error, Figment, Provider, providers::Format};
use serde_json;
use std::collections::HashSet;
pub trait ValidatedProvider {
fn validation_error(&self) -> Option<Error>;
}
impl crate::SuperConfig {
pub fn merge<P: Provider>(mut self, provider: P) -> Self {
self.figment = self.figment.merge(provider);
self.apply_array_merging()
}
pub fn merge_validated<P: Provider + ValidatedProvider>(mut self, provider: P) -> Self {
if let Some(error) = provider.validation_error() {
self.warnings
.push(format!("Provider validation error: {error}"));
}
self.figment = self.figment.merge(provider);
self.apply_array_merging()
}
pub fn merge_opt<P: Provider>(self, provider: Option<P>) -> Self {
match provider {
Some(p) => self.merge(p),
None => self,
}
}
fn apply_array_merging(mut self) -> Self {
if !ArrayMergeHelper::needs_array_merging(&self.figment) {
return self; }
self.figment = ArrayMergeHelper::apply_array_merging(self.figment);
self
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
pub fn print_warnings(&self) {
for warning in &self.warnings {
eprintln!("SuperConfig warning: {warning}");
}
}
}
struct ArrayMergeHelper;
impl ArrayMergeHelper {
fn needs_array_merging(figment: &Figment) -> bool {
if let Ok(value) = figment.extract::<serde_json::Value>() {
Self::contains_merge_patterns(&value)
} else {
false }
}
fn contains_merge_patterns(value: &serde_json::Value) -> bool {
match value {
serde_json::Value::Object(obj) => {
for key in obj.keys() {
if key.ends_with("_add") || key.ends_with("_remove") {
return true;
}
}
obj.values().any(Self::contains_merge_patterns)
}
serde_json::Value::Array(arr) => {
arr.iter().any(Self::contains_merge_patterns)
}
_ => false,
}
}
fn apply_array_merging(figment: Figment) -> Figment {
let json_config = match figment.extract::<serde_json::Value>() {
Ok(config) => config,
Err(_) => return figment, };
eprintln!(
"DEBUG: Before array merging: {}",
serde_json::to_string_pretty(&json_config).unwrap_or_default()
);
let merged_config = Self::merge_object_arrays(json_config);
eprintln!(
"DEBUG: After array merging: {}",
serde_json::to_string_pretty(&merged_config).unwrap_or_default()
);
Figment::new().merge(figment::providers::Json::string(&merged_config.to_string()))
}
fn merge_object_arrays(mut value: serde_json::Value) -> serde_json::Value {
match &mut value {
serde_json::Value::Object(obj) => {
let mut fields_to_remove = Vec::new();
let mut arrays_to_update: Vec<(String, serde_json::Value)> = Vec::new();
let base_fields: HashSet<String> = obj
.keys()
.filter_map(|key| {
if key.ends_with("_add") {
Some(key.strip_suffix("_add").unwrap().to_string())
} else if key.ends_with("_remove") {
Some(key.strip_suffix("_remove").unwrap().to_string())
} else {
None
}
})
.collect();
for base_field in &base_fields {
let add_key = format!("{base_field}_add");
let remove_key = format!("{base_field}_remove");
eprintln!(
"DEBUG: Processing base field '{base_field}' with add_key='{add_key}', remove_key='{remove_key}'"
);
let mut result_array = obj
.get(base_field)
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_else(Vec::new);
eprintln!("DEBUG: Initial base array for '{base_field}': {result_array:?}");
if let Some(add_value) = obj.get(&add_key).and_then(|v| v.as_array()) {
eprintln!("DEBUG: Adding values to '{base_field}': {add_value:?}");
result_array.extend(add_value.clone());
fields_to_remove.push(add_key);
} else {
eprintln!("DEBUG: No _add values found for '{base_field}'");
}
if let Some(remove_value) = obj.get(&remove_key).and_then(|v| v.as_array()) {
eprintln!("DEBUG: Removing values from '{base_field}': {remove_value:?}");
let before_count = result_array.len();
result_array.retain(|item| !remove_value.contains(item));
eprintln!(
"DEBUG: Removed {} items from '{base_field}'",
before_count - result_array.len()
);
fields_to_remove.push(remove_key);
} else {
eprintln!("DEBUG: No _remove values found for '{base_field}'");
}
eprintln!("DEBUG: Final array for '{base_field}': {result_array:?}");
arrays_to_update
.push((base_field.clone(), serde_json::Value::Array(result_array)));
}
for (field, new_array) in arrays_to_update {
obj.insert(field, new_array);
}
for field in fields_to_remove {
obj.remove(&field);
}
for value in obj.values_mut() {
*value = Self::merge_object_arrays(value.clone());
}
serde_json::Value::Object(obj.clone())
}
serde_json::Value::Array(arr) => {
let processed_array: Vec<serde_json::Value> = arr
.iter()
.map(|item| Self::merge_object_arrays(item.clone()))
.collect();
serde_json::Value::Array(processed_array)
}
other => other.clone(),
}
}
}