use serde::ser::{Serialize, SerializeMap, Serializer};
use serde::Serialize as DeriveSerialize;
use crate::config::env::partial_from_env;
use crate::config::errors::ConfigError;
use crate::config::partial::{PartialConfig, PartialIngress, PartialProvider};
use crate::config::EnvSnapshot;
use crate::store::Secret;
const REDACTED: &str = "<redacted>";
pub fn dump_config(
flags: PartialConfig,
env: &EnvSnapshot,
file: PartialConfig,
) -> Result<String, ConfigError> {
let env = partial_from_env(env)?;
let merged = redact(flags.or(env).or(file));
#[allow(clippy::expect_used)]
let value = toml::Value::try_from(&merged).expect("a redacted PartialConfig is TOML-encodable");
#[allow(clippy::expect_used)]
Ok(toml::to_string(&value).expect("a toml::Value is infallibly serializable"))
}
pub fn redact(mut cfg: PartialConfig) -> PartialConfig {
if cfg.api_key.is_some() {
cfg.api_key = Some(Secret::new(REDACTED));
}
if let Some(ingress) = cfg.ingress.as_mut() {
if ingress.token.is_some() {
ingress.token = Some(Secret::new(REDACTED));
}
}
cfg
}
fn clean_f32(v: f32) -> f64 {
v.to_string().parse().unwrap_or(v as f64)
}
#[derive(DeriveSerialize)]
struct Row<'a> {
name: &'a str,
#[serde(flatten)]
inner: &'a PartialProvider,
}
impl Serialize for PartialConfig {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut m = s.serialize_map(None)?;
if let Some(v) = &self.model {
m.serialize_entry("model", v)?;
}
if let Some(v) = &self.base_url {
m.serialize_entry("base_url", v)?;
}
if let Some(v) = &self.api_key {
m.serialize_entry("api_key", v.expose())?;
}
if let Some(v) = &self.output {
m.serialize_entry("output", v)?;
}
if let Some(v) = &self.thinking {
m.serialize_entry("thinking", v)?;
}
if let Some(v) = &self.max_tokens {
m.serialize_entry("max_tokens", v)?;
}
if let Some(v) = &self.temperature {
m.serialize_entry("temperature", &clean_f32(*v))?;
}
if let Some(v) = &self.top_p {
m.serialize_entry("top_p", &clean_f32(*v))?;
}
if let Some(v) = &self.reasoning {
m.serialize_entry("reasoning", v)?;
}
if let Some(v) = &self.stream {
m.serialize_entry("stream", v)?;
}
if let Some(v) = &self.timeout {
m.serialize_entry("timeout", v)?;
}
if let Some(v) = &self.system {
m.serialize_entry("system", v)?;
}
if !self.providers.is_empty() {
let rows: Vec<Row> = self
.providers
.iter()
.map(|(name, inner)| Row { name, inner })
.collect();
m.serialize_entry("provider", &rows)?;
} else if let Some(v) = &self.provider {
m.serialize_entry("provider", v)?;
}
if let Some(v) = &self.ingress {
m.serialize_entry("ingress", v)?;
}
for (key, value) in &self.extra {
if !value.is_null() {
m.serialize_entry(key, value)?;
}
}
m.end()
}
}
impl Serialize for PartialIngress {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut m = s.serialize_map(None)?;
if let Some(v) = &self.listen {
m.serialize_entry("listen", v)?;
}
if let Some(v) = &self.token {
m.serialize_entry("token", v.expose())?;
}
if let Some(v) = &self.lossy {
m.serialize_entry("lossy", v)?;
}
if !self.lossy_overrides.is_empty() {
m.serialize_entry("lossy_overrides", &self.lossy_overrides)?;
}
m.end()
}
}