| @@ -1,6 +1,7 @@ | |||
| use regex::Regex; | |||
| use serde::Deserialize; | |||
| use std::collections::HashMap; | |||
| use std::fmt; | |||
| use std::path::{Path, PathBuf}; | |||
| use thiserror::Error; | |||
| @@ -33,7 +34,7 @@ pub enum ProtocolKind { | |||
| Anthropic, | |||
| } | |||
| #[derive(Debug, Deserialize)] | |||
| #[derive(Deserialize)] | |||
| pub struct ProviderConfig { | |||
| pub protocol: ProtocolKind, | |||
| pub base_url: String, | |||
| @@ -41,6 +42,18 @@ pub struct ProviderConfig { | |||
| pub default_model: String, | |||
| } | |||
| impl fmt::Debug for ProviderConfig { | |||
| fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | |||
| formatter | |||
| .debug_struct("ProviderConfig") | |||
| .field("protocol", &self.protocol) | |||
| .field("base_url", &self.base_url) | |||
| .field("api_token", &"<redacted>") | |||
| .field("default_model", &self.default_model) | |||
| .finish() | |||
| } | |||
| } | |||
| #[derive(Debug, Deserialize)] | |||
| pub struct BenchmarkConfig { | |||
| #[serde(default = "default_benchmark_data_dir")] | |||
| @@ -67,7 +80,7 @@ pub struct DatasetConfig { | |||
| pub split: String, | |||
| } | |||
| #[derive(Debug, Deserialize)] | |||
| #[derive(Deserialize)] | |||
| pub struct AppConfig { | |||
| #[serde(default)] | |||
| pub default_provider: Option<String>, | |||
| @@ -76,6 +89,17 @@ pub struct AppConfig { | |||
| pub benchmarks: BenchmarkConfig, | |||
| } | |||
| impl fmt::Debug for AppConfig { | |||
| fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | |||
| formatter | |||
| .debug_struct("AppConfig") | |||
| .field("default_provider", &self.default_provider) | |||
| .field("providers", &self.providers) | |||
| .field("benchmarks", &self.benchmarks) | |||
| .finish() | |||
| } | |||
| } | |||
| impl AppConfig { | |||
| pub fn load(path: &Path) -> Result<Self, ConfigError> { | |||
| let contents = std::fs::read_to_string(path).map_err(|source| ConfigError::Read { | |||
| @@ -268,4 +292,29 @@ providers: | |||
| assert_eq!(provider.protocol, ProtocolKind::Anthropic); | |||
| assert_eq!(config.benchmarks.data_dir, "data/benchmarks"); | |||
| } | |||
| #[test] | |||
| fn debug_redacts_provider_tokens() { | |||
| let config = AppConfig::load_from_str_with_env( | |||
| r#" | |||
| default_provider: openai | |||
| providers: | |||
| openai: | |||
| protocol: openai | |||
| base_url: https://api.openai.test/v1 | |||
| api_token: ${LQ_TEST_TOKEN} | |||
| default_model: gpt-test | |||
| "#, | |||
| |name| match name { | |||
| "LQ_TEST_TOKEN" => Ok("super-secret-token".to_string()), | |||
| _ => Err(()), | |||
| }, | |||
| ) | |||
| .expect("load config"); | |||
| let debug = format!("{config:?}"); | |||
| assert!(!debug.contains("super-secret-token")); | |||
| assert!(debug.contains("<redacted>")); | |||
| } | |||
| } | |||