use crate::Result;
use std::path::Path;
use tracing::info;
pub fn validate_server_config(config: &crate::common::ServerConfig) -> Result<()> {
if config.name.is_empty() {
return Err(crate::McpToolsError::Config(
"Server name cannot be empty".to_string(),
));
}
if config.port == 0 {
return Err(crate::McpToolsError::Config(
"Server port must be greater than 0".to_string(),
));
}
Ok(())
}
pub fn validate_client_config(config: &crate::common::ClientConfig) -> Result<()> {
if config.name.is_empty() {
return Err(crate::McpToolsError::Config(
"Client name cannot be empty".to_string(),
));
}
if config.server_url.is_empty() {
return Err(crate::McpToolsError::Config(
"Server URL cannot be empty".to_string(),
));
}
Ok(())
}
pub fn format_file_size(size: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = size as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as u64, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
pub fn is_safe_path<P: AsRef<Path>>(path: P) -> bool {
let path = path.as_ref();
if path.to_string_lossy().contains("..") {
return false;
}
if let Ok(canonical) = path.canonicalize() {
let path_str = canonical.to_string_lossy().to_lowercase();
if path_str.starts_with("/etc")
|| path_str.starts_with("/sys")
|| path_str.starts_with("/proc")
|| path_str.starts_with("/dev")
|| path_str.starts_with("/boot")
|| path_str.starts_with("/root")
{
return false;
}
if path_str.starts_with("c:\\windows")
|| path_str.starts_with("c:\\system32")
|| path_str.starts_with("c:\\program files")
{
return false;
}
}
true
}
pub fn generate_session_id() -> String {
format!(
"session-{}-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis(),
uuid::Uuid::new_v4().to_string()[..8].to_string()
)
}
pub fn log_server_startup(config: &crate::common::ServerConfig) {
info!("🚀 Starting MCP Server");
info!(" Name: {}", config.name);
info!(" Description: {}", config.description);
info!(" Version: {}", config.version);
info!(" Address: {}:{}", config.host, config.port);
info!(" Max Connections: {}", config.max_connections);
info!(" Request Timeout: {}s", config.request_timeout_secs);
}
pub fn log_client_startup(config: &crate::common::ClientConfig) {
info!("🔌 Starting MCP Client");
info!(" Name: {}", config.name);
info!(" Version: {}", config.version);
info!(" Server URL: {}", config.server_url);
info!(" Connect Timeout: {}s", config.connect_timeout_secs);
info!(" Request Timeout: {}s", config.request_timeout_secs);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{ClientConfig, ServerConfig};
#[test]
fn test_validate_server_config() {
let mut config = ServerConfig::default();
assert!(validate_server_config(&config).is_ok());
config.name = "".to_string();
assert!(validate_server_config(&config).is_err());
config.name = "Test".to_string();
config.port = 0;
assert!(validate_server_config(&config).is_err());
}
#[test]
fn test_validate_client_config() {
let mut config = ClientConfig::default();
assert!(validate_client_config(&config).is_ok());
config.name = "".to_string();
assert!(validate_client_config(&config).is_err());
config.name = "Test".to_string();
config.server_url = "".to_string();
assert!(validate_client_config(&config).is_err());
}
#[test]
fn test_format_file_size() {
assert_eq!(format_file_size(0), "0 B");
assert_eq!(format_file_size(1023), "1023 B");
assert_eq!(format_file_size(1024), "1.0 KB");
assert_eq!(format_file_size(1536), "1.5 KB");
assert_eq!(format_file_size(1048576), "1.0 MB");
}
#[test]
fn test_is_safe_path() {
assert!(is_safe_path("./test.txt"));
assert!(is_safe_path("test/file.txt"));
assert!(!is_safe_path("../../../etc/passwd"));
assert!(!is_safe_path("test/../../../etc/passwd"));
}
#[test]
fn test_generate_session_id() {
let id1 = generate_session_id();
let id2 = generate_session_id();
assert_ne!(id1, id2);
assert!(id1.starts_with("session-"));
assert!(id2.starts_with("session-"));
}
}