use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub request_timeout_secs: u64,
pub cors: Option<CorsConfig>,
}
impl Default for ServerConfig {
fn default() -> Self {
use crate::config::defaults::server::*;
Self {
host: DEFAULT_HOST.to_string(),
port: DEFAULT_PORT,
request_timeout_secs: DEFAULT_REQUEST_TIMEOUT_SECS,
cors: None,
}
}
}
impl ServerConfig {
pub fn validate(&self) -> Result<(), crate::config::ConfigError> {
if self.port == 0 {
return Err(crate::config::ConfigError::ValidationError(
"Server port cannot be 0".into(),
));
}
if self.request_timeout_secs == 0 {
return Err(crate::config::ConfigError::ValidationError(
"Server request_timeout_secs cannot be 0".into(),
));
}
if self.request_timeout_secs > 86400 {
return Err(crate::config::ConfigError::ValidationError(
"Server request_timeout_secs should not exceed 86400 seconds (24 hours)".into(),
));
}
if let Some(ref cors) = self.cors {
cors.validate()?;
}
Ok(())
}
}
impl crate::config::ValidateConfig for ServerConfig {
fn validate(&self) -> Result<(), crate::config::ConfigError> {
ServerConfig::validate(self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TlsConfig {
cert_path: String,
key_path: String,
}
impl TlsConfig {
pub fn cert_path(&self) -> &str {
&self.cert_path
}
pub fn key_path(&self) -> &str {
&self.key_path
}
}
pub use super::cors::CorsConfig;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_config_default() {
let config = ServerConfig::default();
assert_eq!(config.host, "127.0.0.1"); assert_eq!(config.port, 8080);
assert_eq!(config.request_timeout_secs, 30);
assert!(config.cors.is_none());
}
#[test]
fn test_server_config_validate_valid_port() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 30,
cors: None,
};
assert!(config.validate().is_ok());
}
#[test]
fn test_server_config_validate_zero_port() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 0,
request_timeout_secs: 30,
cors: None,
};
assert!(config.validate().is_err());
}
#[test]
fn test_server_config_validate_zero_timeout() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 0,
cors: None,
};
assert!(config.validate().is_err());
}
#[test]
fn test_server_config_validate_excessive_timeout() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 100000, cors: None,
};
assert!(config.validate().is_err());
}
#[test]
fn test_server_config_serialization() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 9000,
request_timeout_secs: 45,
cors: None,
};
let json = serde_json::to_string(&config).unwrap();
let deserialized: ServerConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.host, "localhost");
assert_eq!(deserialized.port, 9000);
assert_eq!(deserialized.request_timeout_secs, 45);
}
#[test]
fn test_server_config_with_cors() {
let config = ServerConfig {
host: "0.0.0.0".to_string(),
port: 3000,
request_timeout_secs: 30,
cors: Some(CorsConfig {
allowed_origins: vec!["http://localhost:3000".to_string()],
allowed_methods: vec!["GET".to_string()],
allowed_headers: vec!["Authorization".to_string()],
}),
};
assert!(config.cors.is_some());
let cors = config.cors.unwrap();
assert_eq!(cors.allowed_origins.len(), 1);
}
#[test]
fn test_tls_config_getters() {
let config = TlsConfig {
cert_path: "/etc/ssl/cert.pem".to_string(),
key_path: "/etc/ssl/key.pem".to_string(),
};
assert_eq!(config.cert_path(), "/etc/ssl/cert.pem");
assert_eq!(config.key_path(), "/etc/ssl/key.pem");
}
#[test]
fn test_tls_config_serialization() {
let config = TlsConfig {
cert_path: "/path/to/cert.pem".to_string(),
key_path: "/path/to/key.pem".to_string(),
};
let json = serde_json::to_string(&config).unwrap();
let deserialized: TlsConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.cert_path(), "/path/to/cert.pem");
assert_eq!(deserialized.key_path(), "/path/to/key.pem");
}
#[test]
fn test_server_config_validate_with_valid_cors() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 30,
cors: Some(CorsConfig {
allowed_origins: vec!["http://localhost:3000".to_string()],
allowed_methods: vec!["GET".to_string()],
allowed_headers: vec!["Authorization".to_string()],
}),
};
assert!(config.validate().is_ok());
}
#[test]
fn test_server_config_validate_propagates_cors_error() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 30,
cors: Some(CorsConfig {
allowed_origins: vec![],
allowed_methods: vec!["GET".to_string()],
allowed_headers: vec![],
}),
};
let result = config.validate();
assert!(
result.is_err(),
"Invalid CORS should propagate validation error"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("empty"),
"Error should mention empty origins: {}",
err_msg
);
}
#[test]
fn test_server_config_validate_propagates_invalid_origin_error() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 30,
cors: Some(CorsConfig {
allowed_origins: vec!["localhost:3000".to_string()],
allowed_methods: vec!["GET".to_string()],
allowed_headers: vec![],
}),
};
let result = config.validate();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid CORS origin"));
}
#[test]
fn test_server_config_validate_boundary_timeout_86400() {
let config = ServerConfig {
host: "localhost".to_string(),
port: 8080,
request_timeout_secs: 86400,
cors: None,
};
assert!(
config.validate().is_ok(),
"86400 seconds should be allowed (boundary)"
);
}
#[test]
fn test_server_config_deserialization_with_default_fields() {
let json = r#"{"host": "0.0.0.0", "port": 3000}"#;
let config: ServerConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.host, "0.0.0.0");
assert_eq!(config.port, 3000);
assert_eq!(
config.request_timeout_secs, 30,
"Missing field should use Default (30 secs, fail-safe)"
);
assert!(config.cors.is_none(), "Missing cors should default to None");
}
}