//! Error types for the SGLang router core //! //! This module defines error types used throughout the router for worker operations. use std::fmt; /// Worker-related errors #[derive(Debug)] pub enum WorkerError { /// Health check failed HealthCheckFailed { url: String, reason: String }, /// Worker not found WorkerNotFound { url: String }, /// Invalid worker configuration InvalidConfiguration { message: String }, /// Network error NetworkError { url: String, error: String }, /// Worker is at capacity WorkerAtCapacity { url: String }, /// Invalid URL format InvalidUrl { url: String }, } impl fmt::Display for WorkerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { WorkerError::HealthCheckFailed { url, reason } => { write!(f, "Health check failed for worker {}: {}", url, reason) } WorkerError::WorkerNotFound { url } => { write!(f, "Worker not found: {}", url) } WorkerError::InvalidConfiguration { message } => { write!(f, "Invalid worker configuration: {}", message) } WorkerError::NetworkError { url, error } => { write!(f, "Network error for worker {}: {}", url, error) } WorkerError::WorkerAtCapacity { url } => { write!(f, "Worker at capacity: {}", url) } WorkerError::InvalidUrl { url } => { write!(f, "Invalid URL format: {}", url) } } } } impl std::error::Error for WorkerError {} /// Result type for worker operations pub type WorkerResult = Result; /// Convert from reqwest errors to worker errors impl From for WorkerError { fn from(err: reqwest::Error) -> Self { WorkerError::NetworkError { url: err.url().map(|u| u.to_string()).unwrap_or_default(), error: err.to_string(), } } } #[cfg(test)] mod tests { use super::*; use std::error::Error; #[test] fn test_health_check_failed_display() { let error = WorkerError::HealthCheckFailed { url: "http://worker1:8080".to_string(), reason: "Connection refused".to_string(), }; assert_eq!( error.to_string(), "Health check failed for worker http://worker1:8080: Connection refused" ); } #[test] fn test_worker_not_found_display() { let error = WorkerError::WorkerNotFound { url: "http://worker2:8080".to_string(), }; assert_eq!(error.to_string(), "Worker not found: http://worker2:8080"); } #[test] fn test_invalid_configuration_display() { let error = WorkerError::InvalidConfiguration { message: "Missing port number".to_string(), }; assert_eq!( error.to_string(), "Invalid worker configuration: Missing port number" ); } #[test] fn test_network_error_display() { let error = WorkerError::NetworkError { url: "http://worker3:8080".to_string(), error: "Timeout after 30s".to_string(), }; assert_eq!( error.to_string(), "Network error for worker http://worker3:8080: Timeout after 30s" ); } #[test] fn test_worker_at_capacity_display() { let error = WorkerError::WorkerAtCapacity { url: "http://worker4:8080".to_string(), }; assert_eq!(error.to_string(), "Worker at capacity: http://worker4:8080"); } #[test] fn test_worker_error_implements_std_error() { let error = WorkerError::WorkerNotFound { url: "http://test".to_string(), }; // Verify it implements Error trait let _: &dyn Error = &error; assert!(error.source().is_none()); } #[test] fn test_error_send_sync() { fn assert_send_sync() {} assert_send_sync::(); } #[test] fn test_worker_result_type_alias() { // Test Ok variant let result: WorkerResult = Ok(42); assert!(result.is_ok()); assert_eq!(result.unwrap(), 42); // Test Err variant let error = WorkerError::WorkerNotFound { url: "test".to_string(), }; let result: WorkerResult = Err(error); assert!(result.is_err()); } #[test] fn test_empty_url_handling() { // Test empty URLs in error variants let error1 = WorkerError::HealthCheckFailed { url: "".to_string(), reason: "No connection".to_string(), }; assert_eq!( error1.to_string(), "Health check failed for worker : No connection" ); let error2 = WorkerError::NetworkError { url: "".to_string(), error: "DNS failure".to_string(), }; assert_eq!(error2.to_string(), "Network error for worker : DNS failure"); let error3 = WorkerError::WorkerNotFound { url: "".to_string(), }; assert_eq!(error3.to_string(), "Worker not found: "); } #[test] fn test_special_characters_in_messages() { // Test with special characters let error = WorkerError::InvalidConfiguration { message: "Invalid JSON: {\"error\": \"test\"}".to_string(), }; assert_eq!( error.to_string(), "Invalid worker configuration: Invalid JSON: {\"error\": \"test\"}" ); // Test with unicode let error2 = WorkerError::HealthCheckFailed { url: "http://测试:8080".to_string(), reason: "连接被拒绝".to_string(), }; assert_eq!( error2.to_string(), "Health check failed for worker http://测试:8080: 连接被拒绝" ); } #[test] fn test_very_long_error_messages() { let long_message = "A".repeat(10000); let error = WorkerError::InvalidConfiguration { message: long_message.clone(), }; let display = error.to_string(); assert!(display.contains(&long_message)); assert_eq!( display.len(), "Invalid worker configuration: ".len() + long_message.len() ); } // Mock reqwest error for testing conversion #[test] fn test_reqwest_error_conversion() { // Test that NetworkError is the correct variant let network_error = WorkerError::NetworkError { url: "http://example.com".to_string(), error: "connection timeout".to_string(), }; match network_error { WorkerError::NetworkError { url, error } => { assert_eq!(url, "http://example.com"); assert_eq!(error, "connection timeout"); } _ => panic!("Expected NetworkError variant"), } } #[test] fn test_error_equality() { // WorkerError doesn't implement PartialEq, but we can test that // the same error construction produces the same display output let error1 = WorkerError::WorkerNotFound { url: "http://test".to_string(), }; let error2 = WorkerError::WorkerNotFound { url: "http://test".to_string(), }; assert_eq!(error1.to_string(), error2.to_string()); } }