use crate::format::ConfigFormat;
use cirious_codex_result::{codex_ok, CodexError, Result};
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::env;
#[derive(Debug, Default)]
pub struct ConfigBuilder {
internal_map: Value,
}
impl ConfigBuilder {
#[must_use]
pub fn new() -> Self {
Self {
internal_map: Value::Object(serde_json::Map::new()),
}
}
pub fn add_source(mut self, content: &str, format: ConfigFormat) -> Result<Self> {
let parsed = format.parse::<Value>(content).map_err(|e| {
e.with_suggestion(crate::utils::format_suggestion(
"Check the configuration file syntax for invalid formatting.",
))
.with_meta("format", format!("{format:?}"))
})?;
self.merge_value(parsed.value);
codex_ok!(self)
}
#[must_use]
pub fn add_env_prefix(mut self, prefix: &str) -> Self {
for (key, val) in env::vars() {
if let Some(stripped) = key.strip_prefix(prefix) {
let clean_key = stripped.to_lowercase();
let parsed_val = val
.parse::<i64>()
.map(|num| Value::Number(num.into()))
.or_else(|_| val.parse::<bool>().map(Value::Bool))
.unwrap_or(Value::String(val));
if let Value::Object(ref mut map) = self.internal_map {
map.insert(clean_key, parsed_val);
}
}
}
self
}
pub fn build<T: DeserializeOwned>(self) -> Result<T> {
let result = serde_json::from_value(self.internal_map).map_err(|e| {
CodexError::builder(
"CONFIG_BUILD_ERROR",
format!("Failed to map merged configurations to target struct: {e}"),
)
.with_suggestion("Ensure your struct properties match the loaded data and no required fields are missing.")
})?;
codex_ok!(result)
}
fn merge_value(&mut self, other: Value) {
merge(&mut self.internal_map, other);
}
}
fn merge(a: &mut Value, b: Value) {
match (a, b) {
(&mut Value::Object(ref mut a_map), Value::Object(b_map)) => {
for (k, v) in b_map {
merge(a_map.entry(k).or_insert(Value::Null), v);
}
}
(a_val, b_val) => *a_val = b_val,
}
}