| @@ -1,6 +1,7 @@ | |||||
| use regex::Regex; | use regex::Regex; | ||||
| use serde::Deserialize; | use serde::Deserialize; | ||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||
| use std::fmt; | |||||
| use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||
| use thiserror::Error; | use thiserror::Error; | ||||
| @@ -33,7 +34,7 @@ pub enum ProtocolKind { | |||||
| Anthropic, | Anthropic, | ||||
| } | } | ||||
| #[derive(Debug, Deserialize)] | |||||
| #[derive(Deserialize)] | |||||
| pub struct ProviderConfig { | pub struct ProviderConfig { | ||||
| pub protocol: ProtocolKind, | pub protocol: ProtocolKind, | ||||
| pub base_url: String, | pub base_url: String, | ||||
| @@ -41,6 +42,18 @@ pub struct ProviderConfig { | |||||
| pub default_model: String, | 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)] | #[derive(Debug, Deserialize)] | ||||
| pub struct BenchmarkConfig { | pub struct BenchmarkConfig { | ||||
| #[serde(default = "default_benchmark_data_dir")] | #[serde(default = "default_benchmark_data_dir")] | ||||
| @@ -67,7 +80,7 @@ pub struct DatasetConfig { | |||||
| pub split: String, | pub split: String, | ||||
| } | } | ||||
| #[derive(Debug, Deserialize)] | |||||
| #[derive(Deserialize)] | |||||
| pub struct AppConfig { | pub struct AppConfig { | ||||
| #[serde(default)] | #[serde(default)] | ||||
| pub default_provider: Option<String>, | pub default_provider: Option<String>, | ||||
| @@ -76,6 +89,17 @@ pub struct AppConfig { | |||||
| pub benchmarks: BenchmarkConfig, | 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 { | impl AppConfig { | ||||
| pub fn load(path: &Path) -> Result<Self, ConfigError> { | pub fn load(path: &Path) -> Result<Self, ConfigError> { | ||||
| let contents = std::fs::read_to_string(path).map_err(|source| ConfigError::Read { | 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!(provider.protocol, ProtocolKind::Anthropic); | ||||
| assert_eq!(config.benchmarks.data_dir, "data/benchmarks"); | 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>")); | |||||
| } | |||||
| } | } | ||||