use crate::error::{Result, ThingsError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServerConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub logging: LoggingConfig,
pub performance: PerformanceConfig,
pub security: SecurityConfig,
pub cache: CacheConfig,
pub monitoring: MonitoringConfig,
pub features: FeatureFlags,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub name: String,
pub version: String,
pub description: String,
pub max_connections: u32,
pub connection_timeout: u64,
pub request_timeout: u64,
pub graceful_shutdown: bool,
pub shutdown_timeout: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub path: PathBuf,
pub fallback_to_default: bool,
pub pool_size: u32,
pub connection_timeout: u64,
pub query_timeout: u64,
pub enable_query_logging: bool,
pub enable_query_metrics: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub level: String,
pub json_logs: bool,
pub log_file: Option<PathBuf>,
pub console_logs: bool,
pub structured_logs: bool,
pub rotation: LogRotationConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogRotationConfig {
pub enabled: bool,
pub max_file_size_mb: u64,
pub max_files: u32,
pub compress: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
pub enabled: bool,
pub slow_request_threshold_ms: u64,
pub enable_profiling: bool,
pub memory_monitoring: MemoryMonitoringConfig,
pub cpu_monitoring: CpuMonitoringConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryMonitoringConfig {
pub enabled: bool,
pub threshold_percentage: f64,
pub check_interval: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuMonitoringConfig {
pub enabled: bool,
pub threshold_percentage: f64,
pub check_interval: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub authentication: AuthenticationConfig,
pub rate_limiting: RateLimitingConfig,
pub cors: CorsConfig,
pub validation: ValidationConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthenticationConfig {
pub enabled: bool,
pub require_auth: bool,
pub jwt_secret: String,
pub jwt_expiration: u64,
pub api_keys: Vec<ApiKeyConfig>,
pub oauth: Option<OAuth2Config>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyConfig {
pub key: String,
pub key_id: String,
pub permissions: Vec<String>,
pub expires_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OAuth2Config {
pub client_id: String,
pub client_secret: String,
pub token_endpoint: String,
pub scopes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitingConfig {
pub enabled: bool,
pub requests_per_minute: u32,
pub burst_limit: u32,
pub custom_limits: Option<HashMap<String, u32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorsConfig {
pub enabled: bool,
pub allowed_origins: Vec<String>,
pub allowed_methods: Vec<String>,
pub allowed_headers: Vec<String>,
pub exposed_headers: Vec<String>,
pub allow_credentials: bool,
pub max_age: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationConfig {
pub enabled: bool,
pub strict_mode: bool,
pub max_request_size: u64,
pub max_field_length: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
pub enabled: bool,
pub cache_type: String,
pub max_size_mb: u64,
pub ttl_seconds: u64,
pub compression: bool,
pub eviction_policy: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitoringConfig {
pub enabled: bool,
pub metrics_port: u16,
pub health_port: u16,
pub health_checks: bool,
pub metrics_collection: bool,
pub metrics_path: String,
pub health_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
pub struct FeatureFlags {
pub real_time_updates: bool,
pub websocket_server: bool,
pub dashboard: bool,
pub bulk_operations: bool,
pub data_export: bool,
pub backup: bool,
pub hot_reloading: bool,
}
impl McpServerConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[allow(clippy::too_many_lines)]
pub fn from_env() -> Result<Self> {
let mut config = Self::default();
if let Ok(name) = std::env::var("MCP_SERVER_NAME") {
config.server.name = name;
}
if let Ok(version) = std::env::var("MCP_SERVER_VERSION") {
config.server.version = version;
}
if let Ok(description) = std::env::var("MCP_SERVER_DESCRIPTION") {
config.server.description = description;
}
if let Ok(max_connections) = std::env::var("MCP_MAX_CONNECTIONS") {
config.server.max_connections = max_connections
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_MAX_CONNECTIONS value"))?;
}
if let Ok(connection_timeout) = std::env::var("MCP_CONNECTION_TIMEOUT") {
config.server.connection_timeout = connection_timeout
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_CONNECTION_TIMEOUT value"))?;
}
if let Ok(request_timeout) = std::env::var("MCP_REQUEST_TIMEOUT") {
config.server.request_timeout = request_timeout
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_REQUEST_TIMEOUT value"))?;
}
if let Ok(db_path) = std::env::var("MCP_DATABASE_PATH") {
config.database.path = PathBuf::from(db_path);
}
if let Ok(fallback) = std::env::var("MCP_DATABASE_FALLBACK") {
config.database.fallback_to_default = parse_bool(&fallback);
}
if let Ok(pool_size) = std::env::var("MCP_DATABASE_POOL_SIZE") {
config.database.pool_size = pool_size
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_DATABASE_POOL_SIZE value"))?;
}
if let Ok(level) = std::env::var("MCP_LOG_LEVEL") {
config.logging.level = level;
}
if let Ok(json_logs) = std::env::var("MCP_JSON_LOGS") {
config.logging.json_logs = parse_bool(&json_logs);
}
if let Ok(log_file) = std::env::var("MCP_LOG_FILE") {
config.logging.log_file = Some(PathBuf::from(log_file));
}
if let Ok(console_logs) = std::env::var("MCP_CONSOLE_LOGS") {
config.logging.console_logs = parse_bool(&console_logs);
}
if let Ok(enabled) = std::env::var("MCP_PERFORMANCE_ENABLED") {
config.performance.enabled = parse_bool(&enabled);
}
if let Ok(threshold) = std::env::var("MCP_SLOW_REQUEST_THRESHOLD") {
config.performance.slow_request_threshold_ms = threshold.parse().map_err(|_| {
ThingsError::configuration("Invalid MCP_SLOW_REQUEST_THRESHOLD value")
})?;
}
if let Ok(auth_enabled) = std::env::var("MCP_AUTH_ENABLED") {
config.security.authentication.enabled = parse_bool(&auth_enabled);
}
if let Ok(jwt_secret) = std::env::var("MCP_JWT_SECRET") {
config.security.authentication.jwt_secret = jwt_secret;
}
if let Ok(rate_limit_enabled) = std::env::var("MCP_RATE_LIMIT_ENABLED") {
config.security.rate_limiting.enabled = parse_bool(&rate_limit_enabled);
}
if let Ok(requests_per_minute) = std::env::var("MCP_REQUESTS_PER_MINUTE") {
config.security.rate_limiting.requests_per_minute = requests_per_minute
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_REQUESTS_PER_MINUTE value"))?;
}
if let Ok(cache_enabled) = std::env::var("MCP_CACHE_ENABLED") {
config.cache.enabled = parse_bool(&cache_enabled);
}
if let Ok(cache_type) = std::env::var("MCP_CACHE_TYPE") {
config.cache.cache_type = cache_type;
}
if let Ok(max_size) = std::env::var("MCP_CACHE_MAX_SIZE_MB") {
config.cache.max_size_mb = max_size
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_CACHE_MAX_SIZE_MB value"))?;
}
if let Ok(monitoring_enabled) = std::env::var("MCP_MONITORING_ENABLED") {
config.monitoring.enabled = parse_bool(&monitoring_enabled);
}
if let Ok(metrics_port) = std::env::var("MCP_METRICS_PORT") {
config.monitoring.metrics_port = metrics_port
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_METRICS_PORT value"))?;
}
if let Ok(health_port) = std::env::var("MCP_HEALTH_PORT") {
config.monitoring.health_port = health_port
.parse()
.map_err(|_| ThingsError::configuration("Invalid MCP_HEALTH_PORT value"))?;
}
if let Ok(real_time) = std::env::var("MCP_REAL_TIME_UPDATES") {
config.features.real_time_updates = parse_bool(&real_time);
}
if let Ok(websocket) = std::env::var("MCP_WEBSOCKET_SERVER") {
config.features.websocket_server = parse_bool(&websocket);
}
if let Ok(dashboard) = std::env::var("MCP_DASHBOARD") {
config.features.dashboard = parse_bool(&dashboard);
}
if let Ok(bulk_ops) = std::env::var("MCP_BULK_OPERATIONS") {
config.features.bulk_operations = parse_bool(&bulk_ops);
}
if let Ok(data_export) = std::env::var("MCP_DATA_EXPORT") {
config.features.data_export = parse_bool(&data_export);
}
if let Ok(backup) = std::env::var("MCP_BACKUP") {
config.features.backup = parse_bool(&backup);
}
if let Ok(hot_reload) = std::env::var("MCP_HOT_RELOADING") {
config.features.hot_reloading = parse_bool(&hot_reload);
}
Ok(config)
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let content = std::fs::read_to_string(path).map_err(|e| {
ThingsError::Io(std::io::Error::other(format!(
"Failed to read config file {}: {}",
path.display(),
e
)))
})?;
let config = if path.extension().and_then(|s| s.to_str()) == Some("yaml")
|| path.extension().and_then(|s| s.to_str()) == Some("yml")
{
serde_yaml::from_str(&content).map_err(|e| {
ThingsError::configuration(format!("Failed to parse YAML config: {e}"))
})?
} else {
serde_json::from_str(&content).map_err(|e| {
ThingsError::configuration(format!("Failed to parse JSON config: {e}"))
})?
};
Ok(config)
}
pub fn to_file<P: AsRef<Path>>(&self, path: P, format: &str) -> Result<()> {
let path = path.as_ref();
let content = match format {
"yaml" | "yml" => serde_yaml::to_string(self).map_err(|e| {
ThingsError::configuration(format!("Failed to serialize YAML: {e}"))
})?,
"json" => serde_json::to_string_pretty(self).map_err(|e| {
ThingsError::configuration(format!("Failed to serialize JSON: {e}"))
})?,
_ => {
return Err(ThingsError::configuration(format!(
"Unsupported format: {format}"
)))
}
};
std::fs::write(path, content).map_err(|e| {
ThingsError::Io(std::io::Error::other(format!(
"Failed to write config file {}: {}",
path.display(),
e
)))
})?;
Ok(())
}
pub fn validate(&self) -> Result<()> {
if self.server.name.is_empty() {
return Err(ThingsError::configuration("Server name cannot be empty"));
}
if self.server.version.is_empty() {
return Err(ThingsError::configuration("Server version cannot be empty"));
}
if self.server.max_connections == 0 {
return Err(ThingsError::configuration(
"Max connections must be greater than 0",
));
}
if self.database.pool_size == 0 {
return Err(ThingsError::configuration(
"Database pool size must be greater than 0",
));
}
let valid_levels = ["trace", "debug", "info", "warn", "error"];
if !valid_levels.contains(&self.logging.level.as_str()) {
return Err(ThingsError::configuration(format!(
"Invalid log level: {}. Must be one of: {}",
self.logging.level,
valid_levels.join(", ")
)));
}
if self.performance.enabled && self.performance.slow_request_threshold_ms == 0 {
return Err(ThingsError::configuration("Slow request threshold must be greater than 0 when performance monitoring is enabled"));
}
if self.security.authentication.enabled
&& self.security.authentication.jwt_secret.is_empty()
{
return Err(ThingsError::configuration(
"JWT secret cannot be empty when authentication is enabled",
));
}
if self.cache.enabled && self.cache.max_size_mb == 0 {
return Err(ThingsError::configuration(
"Cache max size must be greater than 0 when caching is enabled",
));
}
if self.monitoring.enabled && self.monitoring.metrics_port == 0 {
return Err(ThingsError::configuration(
"Metrics port must be greater than 0 when monitoring is enabled",
));
}
if self.monitoring.enabled && self.monitoring.health_port == 0 {
return Err(ThingsError::configuration(
"Health port must be greater than 0 when monitoring is enabled",
));
}
Ok(())
}
pub fn merge_with(&mut self, other: &McpServerConfig) {
if !other.server.name.is_empty() {
self.server.name.clone_from(&other.server.name);
}
if !other.server.version.is_empty() {
self.server.version.clone_from(&other.server.version);
}
if !other.server.description.is_empty() {
self.server
.description
.clone_from(&other.server.description);
}
if other.server.max_connections > 0 {
self.server.max_connections = other.server.max_connections;
}
if other.server.connection_timeout > 0 {
self.server.connection_timeout = other.server.connection_timeout;
}
if other.server.request_timeout > 0 {
self.server.request_timeout = other.server.request_timeout;
}
if other.database.path != PathBuf::new() {
self.database.path.clone_from(&other.database.path);
}
if other.database.pool_size > 0 {
self.database.pool_size = other.database.pool_size;
}
if !other.logging.level.is_empty() {
self.logging.level.clone_from(&other.logging.level);
}
if other.logging.log_file.is_some() {
self.logging.log_file.clone_from(&other.logging.log_file);
}
self.performance.enabled = other.performance.enabled;
if other.performance.slow_request_threshold_ms > 0 {
self.performance.slow_request_threshold_ms =
other.performance.slow_request_threshold_ms;
}
self.security.authentication.enabled = other.security.authentication.enabled;
if !other.security.authentication.jwt_secret.is_empty() {
self.security
.authentication
.jwt_secret
.clone_from(&other.security.authentication.jwt_secret);
}
self.security.rate_limiting.enabled = other.security.rate_limiting.enabled;
if other.security.rate_limiting.requests_per_minute > 0 {
self.security.rate_limiting.requests_per_minute =
other.security.rate_limiting.requests_per_minute;
}
self.cache.enabled = other.cache.enabled;
if other.cache.max_size_mb > 0 {
self.cache.max_size_mb = other.cache.max_size_mb;
}
self.monitoring.enabled = other.monitoring.enabled;
if other.monitoring.metrics_port > 0 {
self.monitoring.metrics_port = other.monitoring.metrics_port;
}
if other.monitoring.health_port > 0 {
self.monitoring.health_port = other.monitoring.health_port;
}
self.features.real_time_updates = other.features.real_time_updates;
self.features.websocket_server = other.features.websocket_server;
self.features.dashboard = other.features.dashboard;
self.features.bulk_operations = other.features.bulk_operations;
self.features.data_export = other.features.data_export;
self.features.backup = other.features.backup;
self.features.hot_reloading = other.features.hot_reloading;
}
pub fn get_effective_database_path(&self) -> Result<PathBuf> {
if self.database.path.exists() {
return Ok(self.database.path.clone());
}
if self.database.fallback_to_default {
let default_path = Self::get_default_database_path();
if default_path.exists() {
return Ok(default_path);
}
}
Err(ThingsError::configuration(format!(
"Database not found at {} and fallback is {}",
self.database.path.display(),
if self.database.fallback_to_default {
"enabled but default path also not found"
} else {
"disabled"
}
)))
}
#[must_use]
pub fn get_default_database_path() -> PathBuf {
crate::database::get_default_database_path()
}
}
impl Default for McpServerConfig {
#[allow(clippy::too_many_lines)]
fn default() -> Self {
Self {
server: ServerConfig {
name: "things3-mcp-server".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
description: "Things 3 MCP Server".to_string(),
max_connections: 100,
connection_timeout: 30,
request_timeout: 60,
graceful_shutdown: true,
shutdown_timeout: 30,
},
database: DatabaseConfig {
path: Self::get_default_database_path(),
fallback_to_default: true,
pool_size: 10,
connection_timeout: 30,
query_timeout: 60,
enable_query_logging: false,
enable_query_metrics: true,
},
logging: LoggingConfig {
level: "info".to_string(),
json_logs: false,
log_file: None,
console_logs: true,
structured_logs: true,
rotation: LogRotationConfig {
enabled: true,
max_file_size_mb: 100,
max_files: 5,
compress: true,
},
},
performance: PerformanceConfig {
enabled: true,
slow_request_threshold_ms: 1000,
enable_profiling: false,
memory_monitoring: MemoryMonitoringConfig {
enabled: true,
threshold_percentage: 80.0,
check_interval: 60,
},
cpu_monitoring: CpuMonitoringConfig {
enabled: true,
threshold_percentage: 80.0,
check_interval: 60,
},
},
security: SecurityConfig {
authentication: AuthenticationConfig {
enabled: false,
require_auth: false,
jwt_secret: "your-secret-key-change-this-in-production".to_string(),
jwt_expiration: 3600,
api_keys: vec![],
oauth: None,
},
rate_limiting: RateLimitingConfig {
enabled: true,
requests_per_minute: 60,
burst_limit: 10,
custom_limits: None,
},
cors: CorsConfig {
enabled: true,
allowed_origins: vec!["*".to_string()],
allowed_methods: vec![
"GET".to_string(),
"POST".to_string(),
"PUT".to_string(),
"DELETE".to_string(),
],
allowed_headers: vec!["*".to_string()],
exposed_headers: vec![],
allow_credentials: false,
max_age: 86400,
},
validation: ValidationConfig {
enabled: true,
strict_mode: false,
max_request_size: 1024 * 1024, max_field_length: 1000,
},
},
cache: CacheConfig {
enabled: true,
cache_type: "memory".to_string(),
max_size_mb: 100,
ttl_seconds: 3600,
compression: true,
eviction_policy: "lru".to_string(),
},
monitoring: MonitoringConfig {
enabled: true,
metrics_port: 9090,
health_port: 8080,
health_checks: true,
metrics_collection: true,
metrics_path: "/metrics".to_string(),
health_path: "/health".to_string(),
},
features: FeatureFlags {
real_time_updates: true,
websocket_server: true,
dashboard: true,
bulk_operations: true,
data_export: true,
backup: true,
hot_reloading: false,
},
}
}
}
fn parse_bool(value: &str) -> bool {
let lower = value.to_lowercase();
matches!(lower.as_str(), "true" | "1" | "yes" | "on")
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use tempfile::NamedTempFile;
static ENV_MUTEX: Mutex<()> = Mutex::new(());
#[test]
fn test_default_config() {
let config = McpServerConfig::default();
assert_eq!(config.server.name, "things3-mcp-server");
assert!(config.database.fallback_to_default);
assert_eq!(config.logging.level, "info");
assert!(config.performance.enabled);
assert!(!config.security.authentication.enabled);
assert!(config.cache.enabled);
assert!(config.monitoring.enabled);
}
#[test]
fn test_config_validation() {
let config = McpServerConfig::default();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validation_invalid_server_name() {
let mut config = McpServerConfig::default();
config.server.name = String::new();
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_invalid_log_level() {
let mut config = McpServerConfig::default();
config.logging.level = "invalid".to_string();
assert!(config.validate().is_err());
}
#[test]
fn test_config_from_env() {
let _lock = ENV_MUTEX.lock().unwrap();
cleanup_env_vars();
std::env::set_var("MCP_SERVER_NAME", "test-server");
std::env::set_var("MCP_LOG_LEVEL", "debug");
std::env::set_var("MCP_CACHE_ENABLED", "false");
let config = McpServerConfig::from_env().unwrap();
assert_eq!(config.server.name, "test-server");
assert_eq!(config.logging.level, "debug");
assert!(!config.cache.enabled);
cleanup_env_vars();
}
#[test]
fn test_config_to_and_from_file_json() {
let config = McpServerConfig::default();
let temp_file = NamedTempFile::new().unwrap();
let path = temp_file.path().with_extension("json");
config.to_file(&path, "json").unwrap();
let loaded_config = McpServerConfig::from_file(&path).unwrap();
assert_eq!(config.server.name, loaded_config.server.name);
assert_eq!(config.logging.level, loaded_config.logging.level);
}
#[test]
fn test_config_merge() {
let mut config1 = McpServerConfig::default();
let mut config2 = McpServerConfig::default();
config2.server.name = "merged-server".to_string();
config2.logging.level = "debug".to_string();
config2.cache.enabled = false;
config1.merge_with(&config2);
assert_eq!(config1.server.name, "merged-server");
assert_eq!(config1.logging.level, "debug");
assert!(!config1.cache.enabled);
}
#[test]
fn test_parse_bool() {
assert!(parse_bool("true"));
assert!(parse_bool("TRUE"));
assert!(parse_bool("1"));
assert!(parse_bool("yes"));
assert!(parse_bool("on"));
assert!(!parse_bool("false"));
assert!(!parse_bool("0"));
assert!(!parse_bool("no"));
assert!(!parse_bool("off"));
assert!(!parse_bool("invalid"));
}
fn cleanup_env_vars() {
let env_vars = [
"MCP_SERVER_NAME",
"MCP_SERVER_VERSION",
"MCP_SERVER_DESCRIPTION",
"MCP_MAX_CONNECTIONS",
"MCP_CONNECTION_TIMEOUT",
"MCP_REQUEST_TIMEOUT",
"MCP_DATABASE_PATH",
"MCP_DATABASE_FALLBACK",
"MCP_DATABASE_POOL_SIZE",
"MCP_LOG_LEVEL",
"MCP_LOG_FILE",
"MCP_PERFORMANCE_ENABLED",
"MCP_SLOW_REQUEST_THRESHOLD",
"MCP_AUTH_ENABLED",
"MCP_JWT_SECRET",
"MCP_RATE_LIMIT_ENABLED",
"MCP_REQUESTS_PER_MINUTE",
"MCP_CACHE_ENABLED",
"MCP_CACHE_MAX_SIZE_MB",
"MCP_MONITORING_ENABLED",
"MCP_METRICS_PORT",
"MCP_HEALTH_PORT",
"MCP_REAL_TIME_UPDATES",
"MCP_WEBSOCKET_SERVER",
"MCP_DASHBOARD",
"MCP_BULK_OPERATIONS",
"MCP_DATA_EXPORT",
"MCP_BACKUP",
"MCP_HOT_RELOADING",
];
for var in env_vars {
std::env::remove_var(var);
}
}
fn set_test_env_vars() {
std::env::set_var("MCP_SERVER_NAME", "test-server");
std::env::set_var("MCP_SERVER_VERSION", "1.2.3");
std::env::set_var("MCP_SERVER_DESCRIPTION", "Test Description");
std::env::set_var("MCP_MAX_CONNECTIONS", "150");
std::env::set_var("MCP_CONNECTION_TIMEOUT", "30");
std::env::set_var("MCP_REQUEST_TIMEOUT", "60");
std::env::set_var("MCP_DATABASE_PATH", "/test/db.sqlite");
std::env::set_var("MCP_DATABASE_FALLBACK", "false");
std::env::set_var("MCP_DATABASE_POOL_SIZE", "20");
std::env::set_var("MCP_LOG_LEVEL", "debug");
std::env::set_var("MCP_LOG_FILE", "/test/log.txt");
std::env::set_var("MCP_PERFORMANCE_ENABLED", "false");
std::env::set_var("MCP_SLOW_REQUEST_THRESHOLD", "5000");
std::env::set_var("MCP_AUTH_ENABLED", "true");
std::env::set_var("MCP_JWT_SECRET", "test-secret");
std::env::set_var("MCP_RATE_LIMIT_ENABLED", "true");
std::env::set_var("MCP_REQUESTS_PER_MINUTE", "120");
std::env::set_var("MCP_CACHE_ENABLED", "false");
std::env::set_var("MCP_CACHE_MAX_SIZE_MB", "500");
std::env::set_var("MCP_MONITORING_ENABLED", "false");
std::env::set_var("MCP_METRICS_PORT", "9090");
std::env::set_var("MCP_HEALTH_PORT", "8080");
std::env::set_var("MCP_REAL_TIME_UPDATES", "true");
std::env::set_var("MCP_WEBSOCKET_SERVER", "true");
std::env::set_var("MCP_DASHBOARD", "true");
std::env::set_var("MCP_BULK_OPERATIONS", "true");
std::env::set_var("MCP_DATA_EXPORT", "true");
std::env::set_var("MCP_BACKUP", "true");
std::env::set_var("MCP_HOT_RELOADING", "true");
}
fn assert_config_values(config: &McpServerConfig) {
assert_eq!(config.server.name, "test-server");
assert_eq!(config.server.version, "1.2.3");
assert_eq!(config.server.description, "Test Description");
assert_eq!(config.server.max_connections, 150);
assert_eq!(config.server.connection_timeout, 30);
assert_eq!(config.server.request_timeout, 60);
assert_eq!(config.database.path, PathBuf::from("/test/db.sqlite"));
assert!(!config.database.fallback_to_default);
assert_eq!(config.database.pool_size, 20);
assert_eq!(config.logging.level, "debug");
assert_eq!(
config.logging.log_file,
Some(PathBuf::from("/test/log.txt"))
);
assert!(!config.performance.enabled);
assert_eq!(config.performance.slow_request_threshold_ms, 5000);
assert!(config.security.authentication.enabled);
assert_eq!(config.security.authentication.jwt_secret, "test-secret");
assert!(config.security.rate_limiting.enabled);
assert_eq!(config.security.rate_limiting.requests_per_minute, 120);
assert!(!config.cache.enabled);
assert_eq!(config.cache.max_size_mb, 500);
assert!(!config.monitoring.enabled);
assert_eq!(config.monitoring.metrics_port, 9090);
assert_eq!(config.monitoring.health_port, 8080);
assert!(config.features.real_time_updates);
assert!(config.features.websocket_server);
assert!(config.features.dashboard);
assert!(config.features.bulk_operations);
assert!(config.features.data_export);
assert!(config.features.backup);
assert!(config.features.hot_reloading);
}
#[test]
fn test_config_from_env_comprehensive() {
let _lock = ENV_MUTEX.lock().unwrap();
cleanup_env_vars();
set_test_env_vars();
let config = McpServerConfig::from_env().unwrap();
assert_config_values(&config);
cleanup_env_vars();
}
#[test]
fn test_config_from_env_invalid_values() {
let _lock = ENV_MUTEX.lock().unwrap();
cleanup_env_vars();
std::env::set_var("MCP_MAX_CONNECTIONS", "invalid");
let result = McpServerConfig::from_env();
assert!(result.is_err());
std::env::remove_var("MCP_MAX_CONNECTIONS");
std::env::set_var("MCP_CONNECTION_TIMEOUT", "not-a-number");
let result = McpServerConfig::from_env();
assert!(result.is_err());
std::env::remove_var("MCP_CONNECTION_TIMEOUT");
std::env::set_var("MCP_REQUEST_TIMEOUT", "abc");
let result = McpServerConfig::from_env();
assert!(result.is_err());
std::env::remove_var("MCP_REQUEST_TIMEOUT");
cleanup_env_vars();
}
#[test]
fn test_config_from_file_nonexistent() {
let result = McpServerConfig::from_file("/nonexistent/file.json");
assert!(result.is_err());
}
#[test]
fn test_config_from_file_invalid_yaml() {
let temp_file = NamedTempFile::new().unwrap();
let config_path = temp_file.path().with_extension("yaml");
std::fs::write(&config_path, "invalid: yaml: content: [").unwrap();
let result = McpServerConfig::from_file(&config_path);
assert!(result.is_err());
}
#[test]
fn test_config_to_file_invalid_format() {
let config = McpServerConfig::default();
let temp_file = NamedTempFile::new().unwrap();
let config_path = temp_file.path().with_extension("txt");
let result = config.to_file(&config_path, "invalid");
assert!(result.is_err());
}
#[test]
fn test_config_merge_comprehensive() {
let mut config1 = McpServerConfig::default();
config1.server.name = "server1".to_string();
config1.server.max_connections = 100;
config1.cache.enabled = true;
config1.cache.max_size_mb = 200;
config1.performance.enabled = false;
config1.security.authentication.enabled = true;
config1.security.authentication.jwt_secret = "secret1".to_string();
let mut config2 = McpServerConfig::default();
config2.server.name = "server2".to_string();
config2.server.max_connections = 0; config2.cache.enabled = false;
config2.cache.max_size_mb = 0; config2.performance.enabled = true;
config2.security.authentication.enabled = false;
config2.security.authentication.jwt_secret = "secret2".to_string();
config1.merge_with(&config2);
assert_eq!(config1.server.name, "server2");
assert_eq!(config1.server.max_connections, 100); assert!(!config1.cache.enabled); assert_eq!(config1.cache.max_size_mb, 200); assert!(config1.performance.enabled); assert!(!config1.security.authentication.enabled); assert_eq!(config1.security.authentication.jwt_secret, "secret2");
}
#[test]
fn test_config_validation_comprehensive() {
let mut config = McpServerConfig::default();
config.server.name = String::new();
assert!(config.validate().is_err());
config.server.name = "test".to_string();
config.server.version = String::new();
assert!(config.validate().is_err());
config.server.version = "1.0.0".to_string();
config.server.max_connections = 0;
assert!(config.validate().is_err());
config.server.max_connections = 100;
config.database.pool_size = 0;
assert!(config.validate().is_err());
config.database.pool_size = 10;
config.logging.level = "invalid".to_string();
assert!(config.validate().is_err());
config.logging.level = "info".to_string();
config.performance.enabled = true;
config.performance.slow_request_threshold_ms = 0;
assert!(config.validate().is_err());
config.performance.slow_request_threshold_ms = 1000;
config.security.authentication.enabled = true;
config.security.authentication.jwt_secret = String::new();
assert!(config.validate().is_err());
config.security.authentication.jwt_secret = "secret".to_string();
config.cache.enabled = true;
config.cache.max_size_mb = 0;
assert!(config.validate().is_err());
config.cache.max_size_mb = 100;
assert!(config.validate().is_ok());
}
#[test]
fn test_effective_database_path() {
let temp_file = NamedTempFile::new().unwrap();
let db_path = temp_file.path();
let mut config = McpServerConfig::default();
config.database.path = db_path.to_path_buf();
config.database.fallback_to_default = false;
let effective_path = config.get_effective_database_path().unwrap();
assert_eq!(effective_path, db_path);
}
#[test]
fn test_effective_database_path_fallback() {
let mut config = McpServerConfig::default();
config.database.path = PathBuf::from("/nonexistent/path");
config.database.fallback_to_default = true;
let _ = config.get_effective_database_path();
}
}