use std::collections::BTreeMap;
use core::net::SocketAddr;
use crate::config::errors::ConfigError;
use crate::config::partial::{LossyMode, PartialConfig, PartialIngress};
use crate::store::Secret;
const DEFAULT_LISTEN: &str = "127.0.0.1:4891";
const KNOWN_ADAPTATIONS: &[&str] = &["thinking_replay", "document_url_drop"];
#[derive(Clone, Debug, PartialEq)]
pub struct IngressConfig {
pub listen: SocketAddr,
pub token: Option<Secret>,
pub lossy: LossyMode,
pub lossy_overrides: BTreeMap<String, LossyMode>,
}
impl IngressConfig {
pub fn lossy_for(&self, adaptation: &str) -> LossyMode {
self.lossy_overrides
.get(adaptation)
.copied()
.unwrap_or(self.lossy)
}
}
impl PartialConfig {
pub fn resolve_ingress(&self) -> Result<IngressConfig, ConfigError> {
let Some(table) = self.ingress.clone() else {
return Err(err(
"`--serve` needs an `[ingress]` table; the config has none".into(),
));
};
table.into_resolved()
}
}
impl PartialIngress {
pub(crate) fn validate_lossy_overrides(&self) -> Result<(), ConfigError> {
for name in self.lossy_overrides.keys() {
if !KNOWN_ADAPTATIONS.contains(&name.as_str()) {
return Err(err(format!(
"unknown adaptation `{name}` in `lossy_overrides` (known: {})",
KNOWN_ADAPTATIONS.join(", ")
)));
}
}
Ok(())
}
fn into_resolved(self) -> Result<IngressConfig, ConfigError> {
self.validate_lossy_overrides()?;
let spelled = self.listen.as_deref().unwrap_or(DEFAULT_LISTEN);
let listen: SocketAddr = spelled.parse().map_err(|_| {
err(format!(
"`listen` must be an `ip:port` socket address, got `{spelled}`"
))
})?;
if !listen.ip().is_loopback() && self.token.is_none() {
return Err(err(format!(
"refusing a non-loopback `listen` ({listen}) without `token` — set one, or bind loopback"
)));
}
Ok(IngressConfig {
listen,
token: self.token,
lossy: self.lossy.unwrap_or_default(),
lossy_overrides: self.lossy_overrides,
})
}
}
fn err(detail: String) -> ConfigError {
ConfigError::Ingress { detail }
}