use std::collections::BTreeMap;
use crate::ConfigError;
pub(crate) const KEYS: &[&str] = &[
"bind",
"grpc_bind",
"upstream",
"index",
"tokens",
"allow_cleartext_mutation",
"tls_cert",
"tls_key",
"tls_client_ca",
"log_requests",
"otlp_endpoint",
"service_name",
"diag_baseline",
"debug_directive_key",
"directive_admin_token",
"debug_endpoints",
"log_diagnostic_captures",
"admin_passthrough_cluster",
"admin_passthrough_prefixes",
"admin_passthrough_endpoint",
"cursor_affinity_key",
"passthrough_cluster",
"passthrough_endpoint",
"passthrough_indices",
"forward_client_headers",
"forward_header_deny",
"capture_default",
"capture_kafka_brokers",
"capture_topic",
"capture_redact",
"capture_kafka_ca",
"capture_kafka_client_cert",
"capture_kafka_client_key",
"capture_max_inflight",
"capture_max_attempts",
"capture_backoff_ms",
"capture_wal_dir",
"capture_wal_max_bytes",
"fanout_kafka_brokers",
"fanout_topic",
"fanout_kafka_ca",
"fanout_kafka_client_cert",
"fanout_kafka_client_key",
"fanout_body_encoding",
"fanout_async_default",
"fanout_expand_delete_by_query",
"etcd_endpoints",
"etcd_directives_key",
];
#[must_use]
pub(crate) fn env_name(key: &str) -> String {
format!("OSPROXY_{}", key.to_ascii_uppercase())
}
fn canonical(key: &str) -> Option<&'static str> {
KEYS.iter().copied().find(|k| *k == key)
}
fn canonical_in(section: Option<&str>, key: &str) -> Option<&'static str> {
section
.and_then(|s| canonical(&format!("{s}_{key}")))
.or_else(|| canonical(key))
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Raw {
values: BTreeMap<&'static str, String>,
}
impl Raw {
#[must_use]
pub(crate) fn get(&self, key: &'static str) -> Option<&str> {
self.values.get(key).map(String::as_str)
}
fn set(&mut self, key: &'static str, value: String) {
if value.is_empty() {
return;
}
self.values.insert(key, value);
}
#[must_use]
pub(crate) fn from_env() -> Self {
let mut raw = Raw::default();
for &key in KEYS {
if let Ok(value) = std::env::var(env_name(key)) {
raw.set(key, value);
}
}
raw
}
pub(crate) fn from_file(text: &str) -> Result<Self, ConfigError> {
let mut raw = Raw::default();
let mut section: Option<String> = None;
for (n, line) in text.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(name) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
let name = name.trim();
section = (!name.is_empty()).then(|| name.to_owned());
continue;
}
let (key, value) = line.split_once('=').ok_or_else(|| {
ConfigError::invalid("file", format!("line {}: expected `key = value`", n + 1))
})?;
let key = key.trim();
let canonical =
canonical_in(section.as_deref(), key).ok_or_else(|| ConfigError::unknown(key))?;
raw.set(canonical, unquote(value.trim()).to_owned());
}
Ok(raw)
}
pub(crate) fn from_flags<I: IntoIterator<Item = String>>(args: I) -> Result<Self, ConfigError> {
let mut raw = Raw::default();
let mut args = args.into_iter();
while let Some(arg) = args.next() {
let body = arg.strip_prefix("--").ok_or_else(|| {
ConfigError::invalid("flags", format!("unexpected argument {arg:?}"))
})?;
let (name, value) = if let Some((name, value)) = body.split_once('=') {
(name.to_owned(), value.to_owned())
} else {
let value = args.next().ok_or_else(|| {
ConfigError::invalid("flags", format!("--{body} needs a value"))
})?;
(body.to_owned(), value)
};
let key = name.replace('-', "_");
let canonical = canonical(&key).ok_or_else(|| ConfigError::unknown(&name))?;
raw.set(canonical, value);
}
Ok(raw)
}
pub(crate) fn from_pairs(pairs: &[(&str, &str)]) -> Result<Self, ConfigError> {
let mut raw = Raw::default();
for (key, value) in pairs {
let canonical = canonical(key).ok_or_else(|| ConfigError::unknown(*key))?;
raw.set(canonical, (*value).to_owned());
}
Ok(raw)
}
#[must_use]
pub(crate) fn layered(file: Raw, env: Raw, flags: Raw) -> Raw {
let mut merged = file;
merged.values.extend(env.values);
merged.values.extend(flags.values);
merged
}
}
fn unquote(value: &str) -> &str {
let bytes = value.as_bytes();
if value.len() >= 2
&& (bytes[0] == b'"' || bytes[0] == b'\'')
&& bytes[bytes.len() - 1] == bytes[0]
{
&value[1..value.len() - 1]
} else {
value
}
}