use super::error::{ConfigError, Result};
use super::schema::OpenCodeConfig;
pub fn validate_config(config: &OpenCodeConfig) -> Result<()> {
let mut errors = Vec::new();
if let Some(ref model) = config.model {
validate_model_id(model, &mut errors);
}
if let Some(ref small_model) = config.small_model {
validate_model_id(small_model, &mut errors);
}
if let Some(ref providers) = config.provider {
for (provider_id, provider_config) in providers {
validate_provider(provider_id, provider_config, &mut errors);
}
}
if let (Some(disabled), Some(enabled)) = (&config.disabled_providers, &config.enabled_providers)
{
for provider_id in disabled {
if enabled.contains(provider_id) {
errors.push(format!(
"Provider '{}' is in both disabled_providers and enabled_providers (disabled takes priority)",
provider_id
));
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(ConfigError::Validation(errors.join("; ")))
}
}
fn validate_model_id(model_id: &str, errors: &mut Vec<String>) {
if model_id.contains('/') {
let parts: Vec<&str> = model_id.splitn(2, '/').collect();
if parts[0].is_empty() {
errors.push(format!(
"Invalid model ID '{}': provider part is empty",
model_id
));
}
if parts[1].is_empty() {
errors.push(format!(
"Invalid model ID '{}': model part is empty",
model_id
));
}
} else {
errors.push(format!(
"Invalid model ID '{}': must be in 'provider/model' format",
model_id
));
}
}
fn validate_provider(
provider_id: &str,
provider: &super::schema::ProviderConfig,
errors: &mut Vec<String>,
) {
if provider_id.contains(' ') || provider_id.contains('\t') {
errors.push(format!("Provider ID '{}' contains whitespace", provider_id));
}
if let Some(ref npm) = provider.npm {
if npm.is_empty() {
errors.push(format!(
"Provider '{}' has empty npm package name",
provider_id
));
}
}
if let Some(ref models) = provider.models {
for (model_id, model_config) in models {
if model_id.is_empty() {
errors.push(format!(
"Provider '{}' has a model with empty ID",
provider_id
));
}
if let Some(ref limit) = model_config.limit {
if limit.context == Some(0) {
errors.push(format!(
"Provider '{}' model '{}' has context limit of 0",
provider_id, model_id
));
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::super::schema::*;
use super::*;
use std::collections::HashMap;
#[test]
fn test_validate_valid_config() {
let config = OpenCodeConfig {
model: Some("anthropic/claude-sonnet-4-5".to_string()),
provider: Some({
let mut providers = HashMap::new();
providers.insert(
"anthropic".to_string(),
ProviderConfig {
options: Some(HashMap::new()),
..Default::default()
},
);
providers
}),
..Default::default()
};
assert!(validate_config(&config).is_ok());
}
#[test]
fn test_validate_invalid_model_id() {
let config = OpenCodeConfig {
model: Some("invalid-model-id".to_string()),
..Default::default()
};
let result = validate_config(&config);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("provider/model"));
}
#[test]
fn test_validate_empty_provider_id() {
let config = OpenCodeConfig {
model: Some("/claude-sonnet-4-5".to_string()),
..Default::default()
};
let result = validate_config(&config);
assert!(result.is_err());
}
#[test]
fn test_validate_provider_whitespace_id() {
let config = OpenCodeConfig {
provider: Some({
let mut providers = HashMap::new();
providers.insert("has space".to_string(), ProviderConfig::default());
providers
}),
..Default::default()
};
let result = validate_config(&config);
assert!(result.is_err());
}
#[test]
fn test_validate_disabled_enabled_conflict() {
let config = OpenCodeConfig {
disabled_providers: Some(vec!["anthropic".to_string()]),
enabled_providers: Some(vec!["anthropic".to_string()]),
..Default::default()
};
let result = validate_config(&config);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("disabled_providers and enabled_providers")
);
}
#[test]
fn test_validate_empty_config() {
let config = OpenCodeConfig::default();
assert!(validate_config(&config).is_ok());
}
}