litellm_rs/config/models/
server.rs1use super::*;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct ServerConfig {
11 #[serde(default = "default_host")]
14 pub host: String,
15 #[serde(default = "default_port")]
17 pub port: u16,
18 pub workers: Option<usize>,
20 #[serde(skip_serializing_if = "Option::is_none")]
25 pub max_connections: Option<usize>,
26 #[serde(default = "default_timeout")]
30 pub timeout: u64,
31 #[serde(default = "default_max_body_size")]
34 pub max_body_size: usize,
35 #[serde(default)]
37 pub dev_mode: bool,
38 pub tls: Option<TlsConfig>,
40 #[serde(default)]
42 pub cors: CorsConfig,
43 #[serde(default)]
45 pub features: Vec<String>,
46 #[serde(default)]
49 pub trusted_proxies: Vec<String>,
50 #[serde(default = "default_stream_idle_timeout")]
54 pub stream_idle_timeout: u64,
55}
56
57fn default_stream_idle_timeout() -> u64 {
58 300 }
60
61impl Default for ServerConfig {
62 fn default() -> Self {
63 Self {
64 host: default_host(),
65 port: default_port(),
66 workers: None,
67 max_connections: None,
68 timeout: default_timeout(),
69 max_body_size: default_max_body_size(),
70 dev_mode: false,
71 tls: None,
72 cors: CorsConfig::default(),
73 features: Vec::new(),
74 trusted_proxies: Vec::new(),
75 stream_idle_timeout: default_stream_idle_timeout(),
76 }
77 }
78}
79
80impl ServerConfig {
81 pub fn merge(mut self, other: Self) -> Self {
83 if other.host != default_host() {
84 self.host = other.host;
85 }
86 if other.port != default_port() {
87 self.port = other.port;
88 }
89 if other.workers.is_some() {
90 self.workers = other.workers;
91 }
92 if other.max_connections.is_some() {
93 self.max_connections = other.max_connections;
94 }
95 if other.timeout != default_timeout() {
96 self.timeout = other.timeout;
97 }
98 if other.max_body_size != default_max_body_size() {
99 self.max_body_size = other.max_body_size;
100 }
101 if other.dev_mode {
102 self.dev_mode = other.dev_mode;
103 }
104 if other.tls.is_some() {
105 self.tls = other.tls;
106 }
107 self.cors = self.cors.merge(other.cors);
108 if !other.features.is_empty() {
109 self.features = other.features;
110 }
111 if !other.trusted_proxies.is_empty() {
112 self.trusted_proxies = other.trusted_proxies;
113 }
114 if other.stream_idle_timeout != default_stream_idle_timeout() {
115 self.stream_idle_timeout = other.stream_idle_timeout;
116 }
117 self
118 }
119
120 pub fn address(&self) -> String {
122 format!("{}:{}", self.host, self.port)
123 }
124
125 pub fn is_tls_enabled(&self) -> bool {
127 self.tls.is_some()
128 }
129
130 pub fn worker_count(&self) -> usize {
132 self.workers
133 .unwrap_or_else(|| std::thread::available_parallelism().map_or(1, |n| n.get()))
134 }
135
136 pub fn validate(&self) -> Result<(), String> {
138 if self.port == 0 {
139 return Err("Port must be between 1 and 65535".to_string());
140 }
141
142 if self.timeout == 0 {
143 return Err("Timeout cannot be 0".to_string());
144 }
145
146 if self.max_body_size == 0 {
147 return Err("Max body size cannot be 0".to_string());
148 }
149
150 if self.max_connections.is_some_and(|limit| limit < 2) {
151 return Err("server.max_connections must be at least 2".to_string());
152 }
153
154 if let Some(tls) = &self.tls {
155 tls.validate()?;
156 }
157
158 Ok(())
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(deny_unknown_fields)]
165pub struct TlsConfig {
166 pub cert_file: String,
168 pub key_file: String,
170 pub ca_file: Option<String>,
172 #[serde(default)]
174 pub require_client_cert: bool,
175 #[serde(default)]
177 pub http2: bool,
178}
179
180impl TlsConfig {
181 pub fn validate(&self) -> Result<(), String> {
183 if self.cert_file.is_empty() {
184 return Err("TLS certificate file path is required".to_string());
185 }
186
187 if self.key_file.is_empty() {
188 return Err("TLS private key file path is required".to_string());
189 }
190
191 if self.ca_file.is_some() {
192 return Err(
193 "tls.ca_file is unsupported until client certificate auth is implemented"
194 .to_string(),
195 );
196 }
197
198 if self.require_client_cert {
199 return Err(
200 "tls.require_client_cert is not implemented yet; set it to false".to_string(),
201 );
202 }
203
204 if self.http2 {
205 return Err("tls.http2 is unsupported; set it to false to use HTTP/1 TLS".to_string());
206 }
207
208 Ok(())
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(deny_unknown_fields)]
215pub struct CorsConfig {
216 #[serde(default = "default_true")]
218 pub enabled: bool,
219 #[serde(default)]
221 pub allowed_origins: Vec<String>,
222 #[serde(default = "default_cors_methods")]
224 pub allowed_methods: Vec<String>,
225 #[serde(default = "default_cors_headers")]
227 pub allowed_headers: Vec<String>,
228 #[serde(default = "default_cors_max_age")]
230 pub max_age: u32,
231 #[serde(default)]
233 pub allow_credentials: bool,
234}
235
236impl Default for CorsConfig {
237 fn default() -> Self {
238 Self {
239 enabled: true,
240 allowed_origins: vec![], allowed_methods: default_cors_methods(),
242 allowed_headers: default_cors_headers(),
243 max_age: default_cors_max_age(),
244 allow_credentials: false,
245 }
246 }
247}
248
249impl CorsConfig {
250 pub fn merge(mut self, other: Self) -> Self {
252 if !other.enabled {
253 self.enabled = other.enabled;
254 }
255 if !other.allowed_origins.is_empty() {
256 self.allowed_origins = other.allowed_origins;
257 }
258 if other.allowed_methods != default_cors_methods() {
259 self.allowed_methods = other.allowed_methods;
260 }
261 if other.allowed_headers != default_cors_headers() {
262 self.allowed_headers = other.allowed_headers;
263 }
264 if other.max_age != default_cors_max_age() {
265 self.max_age = other.max_age;
266 }
267 if other.allow_credentials {
268 self.allow_credentials = other.allow_credentials;
269 }
270 self
271 }
272
273 pub fn allows_all_origins(&self) -> bool {
275 self.allowed_origins.iter().any(|origin| origin == "*")
276 }
277
278 pub fn validate(&self) -> Result<(), String> {
280 if self.enabled {
281 if self.allows_all_origins() && self.allow_credentials {
282 return Err("CORS cannot allow all origins (*) when credentials are enabled for security reasons".to_string());
283 }
284
285 if self.allows_all_origins() {
287 warn!("CORS allows all origins. This may be insecure for production.");
288 }
289 }
290 Ok(())
291 }
292}
293
294fn default_cors_methods() -> Vec<String> {
295 vec![
296 "GET".to_string(),
297 "POST".to_string(),
298 "PUT".to_string(),
299 "DELETE".to_string(),
300 "OPTIONS".to_string(),
301 ]
302}
303
304fn default_cors_headers() -> Vec<String> {
305 vec![
306 "authorization".to_string(),
307 "content-type".to_string(),
308 "x-requested-with".to_string(),
309 ]
310}
311
312fn default_cors_max_age() -> u32 {
313 3600
314}
315
316#[cfg(test)]
317#[path = "server_tests.rs"]
318mod tests;