opencode_provider_manager/config_core/
validate.rs1use super::error::{ConfigError, Result};
4use super::schema::OpenCodeConfig;
5
6pub fn validate_config(config: &OpenCodeConfig) -> Result<()> {
14 let mut errors = Vec::new();
15
16 if let Some(ref model) = config.model {
18 validate_model_id(model, &mut errors);
19 }
20
21 if let Some(ref small_model) = config.small_model {
22 validate_model_id(small_model, &mut errors);
23 }
24
25 if let Some(ref providers) = config.provider {
27 for (provider_id, provider_config) in providers {
28 validate_provider(provider_id, provider_config, &mut errors);
29 }
30 }
31
32 if let (Some(disabled), Some(enabled)) = (&config.disabled_providers, &config.enabled_providers)
34 {
35 for provider_id in disabled {
36 if enabled.contains(provider_id) {
37 errors.push(format!(
38 "Provider '{}' is in both disabled_providers and enabled_providers (disabled takes priority)",
39 provider_id
40 ));
41 }
42 }
43 }
44
45 if errors.is_empty() {
46 Ok(())
47 } else {
48 Err(ConfigError::Validation(errors.join("; ")))
49 }
50}
51
52fn validate_model_id(model_id: &str, errors: &mut Vec<String>) {
54 if model_id.contains('/') {
55 let parts: Vec<&str> = model_id.splitn(2, '/').collect();
56 if parts[0].is_empty() {
57 errors.push(format!(
58 "Invalid model ID '{}': provider part is empty",
59 model_id
60 ));
61 }
62 if parts[1].is_empty() {
63 errors.push(format!(
64 "Invalid model ID '{}': model part is empty",
65 model_id
66 ));
67 }
68 } else {
69 errors.push(format!(
70 "Invalid model ID '{}': must be in 'provider/model' format",
71 model_id
72 ));
73 }
74}
75
76fn validate_provider(
78 provider_id: &str,
79 provider: &super::schema::ProviderConfig,
80 errors: &mut Vec<String>,
81) {
82 if provider_id.contains(' ') || provider_id.contains('\t') {
84 errors.push(format!("Provider ID '{}' contains whitespace", provider_id));
85 }
86
87 if let Some(ref npm) = provider.npm {
89 if npm.is_empty() {
90 errors.push(format!(
91 "Provider '{}' has empty npm package name",
92 provider_id
93 ));
94 }
95 }
96
97 if let Some(ref models) = provider.models {
99 for (model_id, model_config) in models {
100 if model_id.is_empty() {
101 errors.push(format!(
102 "Provider '{}' has a model with empty ID",
103 provider_id
104 ));
105 }
106 if let Some(ref limit) = model_config.limit {
107 if limit.context == Some(0) {
108 errors.push(format!(
109 "Provider '{}' model '{}' has context limit of 0",
110 provider_id, model_id
111 ));
112 }
113 }
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::super::schema::*;
121 use super::*;
122 use std::collections::HashMap;
123
124 #[test]
125 fn test_validate_valid_config() {
126 let config = OpenCodeConfig {
127 model: Some("anthropic/claude-sonnet-4-5".to_string()),
128 provider: Some({
129 let mut providers = HashMap::new();
130 providers.insert(
131 "anthropic".to_string(),
132 ProviderConfig {
133 options: Some(HashMap::new()),
134 ..Default::default()
135 },
136 );
137 providers
138 }),
139 ..Default::default()
140 };
141
142 assert!(validate_config(&config).is_ok());
143 }
144
145 #[test]
146 fn test_validate_invalid_model_id() {
147 let config = OpenCodeConfig {
148 model: Some("invalid-model-id".to_string()),
149 ..Default::default()
150 };
151
152 let result = validate_config(&config);
153 assert!(result.is_err());
154 assert!(result.unwrap_err().to_string().contains("provider/model"));
155 }
156
157 #[test]
158 fn test_validate_empty_provider_id() {
159 let config = OpenCodeConfig {
161 model: Some("/claude-sonnet-4-5".to_string()),
162 ..Default::default()
163 };
164
165 let result = validate_config(&config);
166 assert!(result.is_err());
167 }
168
169 #[test]
170 fn test_validate_provider_whitespace_id() {
171 let config = OpenCodeConfig {
172 provider: Some({
173 let mut providers = HashMap::new();
174 providers.insert("has space".to_string(), ProviderConfig::default());
175 providers
176 }),
177 ..Default::default()
178 };
179
180 let result = validate_config(&config);
181 assert!(result.is_err());
182 }
183
184 #[test]
185 fn test_validate_disabled_enabled_conflict() {
186 let config = OpenCodeConfig {
187 disabled_providers: Some(vec!["anthropic".to_string()]),
188 enabled_providers: Some(vec!["anthropic".to_string()]),
189 ..Default::default()
190 };
191
192 let result = validate_config(&config);
193 assert!(result.is_err());
194 assert!(
195 result
196 .unwrap_err()
197 .to_string()
198 .contains("disabled_providers and enabled_providers")
199 );
200 }
201
202 #[test]
203 fn test_validate_empty_config() {
204 let config = OpenCodeConfig::default();
205 assert!(validate_config(&config).is_ok());
206 }
207}