use std::collections::HashMap;
use std::path::PathBuf;
use crate::config::{ConfigError, PartialServerConfig};
pub fn parse_env_map<I>(vars: I) -> Result<PartialServerConfig, ConfigError>
where
I: IntoIterator<Item = (String, String)>,
{
let map: HashMap<String, String> = vars.into_iter().collect();
let mut out = PartialServerConfig::default();
if let Some(v) = map.get("OXIBONSAI_HOST") {
out.host = Some(v.to_string());
}
if let Some(v) = map.get("OXIBONSAI_MODEL_PATH") {
out.model_path = Some(PathBuf::from(v));
}
if let Some(v) = map.get("OXIBONSAI_TOKENIZER_PATH") {
out.tokenizer_path = Some(PathBuf::from(v));
}
if let Some(v) = map.get("OXIBONSAI_TOKENIZER_KIND") {
out.tokenizer_kind = Some(v.to_string());
}
if let Some(v) = map.get("OXIBONSAI_BEARER_TOKEN") {
out.bearer_token = Some(v.to_string());
}
if let Some(v) = map.get("OXIBONSAI_LOG_LEVEL") {
out.log_level = Some(v.to_string());
}
if let Some(v) = map.get("OXIBONSAI_METRICS_PATH") {
out.metrics_path = Some(v.to_string());
}
if let Some(v) = map.get("OXIBONSAI_QUANTIZATION_HINT") {
out.quantization_hint = Some(v.to_string());
}
if let Some(v) = map.get("OXIBONSAI_PORT") {
out.port = Some(parse_u16("OXIBONSAI_PORT", v)?);
}
if let Some(v) = map.get("OXIBONSAI_MAX_TOKENS") {
out.default_max_tokens = Some(parse_usize("OXIBONSAI_MAX_TOKENS", v)?);
}
if let Some(v) = map.get("OXIBONSAI_TEMPERATURE") {
out.default_temperature = Some(parse_f32("OXIBONSAI_TEMPERATURE", v)?);
}
if let Some(v) = map.get("OXIBONSAI_TOP_P") {
out.default_top_p = Some(parse_f32("OXIBONSAI_TOP_P", v)?);
}
if let Some(v) = map.get("OXIBONSAI_MAX_INPUT_TOKENS") {
out.max_input_tokens = Some(parse_usize("OXIBONSAI_MAX_INPUT_TOKENS", v)?);
}
if let Some(v) = map.get("OXIBONSAI_MAX_CONCURRENT") {
out.max_concurrent_requests = Some(parse_usize("OXIBONSAI_MAX_CONCURRENT", v)?);
}
if let Some(v) = map.get("OXIBONSAI_REQUEST_TIMEOUT_MS") {
out.per_request_timeout_ms = Some(parse_u64("OXIBONSAI_REQUEST_TIMEOUT_MS", v)?);
}
if let Some(v) = map.get("OXIBONSAI_SEED") {
out.seed = Some(parse_u64("OXIBONSAI_SEED", v)?);
}
if let Some(v) = map.get("OXIBONSAI_METRICS_ENABLED") {
out.metrics_enabled = Some(parse_bool("OXIBONSAI_METRICS_ENABLED", v)?);
}
Ok(out)
}
pub fn parse_process_env() -> Result<PartialServerConfig, ConfigError> {
parse_env_map(std::env::vars())
}
fn parse_u16(name: &str, value: &str) -> Result<u16, ConfigError> {
value.parse::<u16>().map_err(|e| ConfigError::EnvParse {
name: name.to_string(),
reason: format!("expected u16 ({e})"),
})
}
fn parse_u64(name: &str, value: &str) -> Result<u64, ConfigError> {
value.parse::<u64>().map_err(|e| ConfigError::EnvParse {
name: name.to_string(),
reason: format!("expected u64 ({e})"),
})
}
fn parse_usize(name: &str, value: &str) -> Result<usize, ConfigError> {
value.parse::<usize>().map_err(|e| ConfigError::EnvParse {
name: name.to_string(),
reason: format!("expected usize ({e})"),
})
}
fn parse_f32(name: &str, value: &str) -> Result<f32, ConfigError> {
value.parse::<f32>().map_err(|e| ConfigError::EnvParse {
name: name.to_string(),
reason: format!("expected f32 ({e})"),
})
}
fn parse_bool(name: &str, value: &str) -> Result<bool, ConfigError> {
match value.trim().to_ascii_lowercase().as_str() {
"1" | "true" | "yes" | "on" => Ok(true),
"0" | "false" | "no" | "off" => Ok(false),
other => Err(ConfigError::EnvParse {
name: name.to_string(),
reason: format!("expected bool, got {other:?}"),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_env_yields_empty_partial() {
let p = parse_env_map(std::iter::empty()).expect("should parse");
assert_eq!(p, PartialServerConfig::default());
}
#[test]
fn host_port_are_parsed() {
let p = parse_env_map([
("OXIBONSAI_HOST".to_string(), "1.2.3.4".to_string()),
("OXIBONSAI_PORT".to_string(), "9090".to_string()),
])
.expect("parse");
assert_eq!(p.host.as_deref(), Some("1.2.3.4"));
assert_eq!(p.port, Some(9090));
}
#[test]
fn bad_port_errors() {
let err = parse_env_map([("OXIBONSAI_PORT".to_string(), "abc".to_string())])
.expect_err("should fail");
assert!(matches!(err, ConfigError::EnvParse { .. }));
}
#[test]
fn bool_yes_is_true() {
assert!(parse_bool("X", "yes").expect("parse"));
assert!(parse_bool("X", "on").expect("parse"));
assert!(parse_bool("X", "1").expect("parse"));
assert!(!parse_bool("X", "no").expect("parse"));
assert!(!parse_bool("X", "off").expect("parse"));
assert!(!parse_bool("X", "0").expect("parse"));
}
#[test]
fn bool_bad_errors() {
assert!(parse_bool("X", "maybe").is_err());
}
}