mockforge_core/
server_utils.rs

1//! Common server utilities for MockForge
2
3use std::net::SocketAddr;
4
5/// Create a SocketAddr for server binding
6pub fn create_socket_addr(host: &str, port: u16) -> Result<SocketAddr, String> {
7    format!("{}:{}", host, port)
8        .parse()
9        .map_err(|e| format!("Invalid socket address {}:{}: {}", host, port, e))
10}
11
12/// Create a standard IPv4 localhost SocketAddr
13pub fn localhost_socket_addr(port: u16) -> SocketAddr {
14    SocketAddr::from(([127, 0, 0, 1], port))
15}
16
17/// Create a standard IPv4 wildcard SocketAddr (listen on all interfaces)
18pub fn wildcard_socket_addr(port: u16) -> SocketAddr {
19    SocketAddr::from(([0, 0, 0, 0], port))
20}
21
22/// Server startup configuration
23#[derive(Debug, Clone)]
24pub struct ServerConfig {
25    pub host: String,
26    pub port: u16,
27    pub server_type: ServerType,
28}
29
30#[derive(Debug, Clone)]
31pub enum ServerType {
32    HTTP,
33    WebSocket,
34    GRPC,
35}
36
37impl ServerConfig {
38    /// Create a new server configuration
39    pub fn new(host: String, port: u16, server_type: ServerType) -> Self {
40        Self {
41            host,
42            port,
43            server_type,
44        }
45    }
46
47    /// Create HTTP server configuration
48    pub fn http(port: u16) -> Self {
49        Self::new("0.0.0.0".to_string(), port, ServerType::HTTP)
50    }
51
52    /// Create WebSocket server configuration
53    pub fn websocket(port: u16) -> Self {
54        Self::new("0.0.0.0".to_string(), port, ServerType::WebSocket)
55    }
56
57    /// Create gRPC server configuration
58    pub fn grpc(port: u16) -> Self {
59        Self::new("0.0.0.0".to_string(), port, ServerType::GRPC)
60    }
61
62    /// Get the socket address for this configuration
63    pub fn socket_addr(&self) -> Result<SocketAddr, String> {
64        create_socket_addr(&self.host, self.port)
65    }
66
67    /// Get a formatted server description
68    pub fn description(&self) -> String {
69        match self.server_type {
70            ServerType::HTTP => format!("HTTP server on {}:{}", self.host, self.port),
71            ServerType::WebSocket => format!("WebSocket server on {}:{}", self.host, self.port),
72            ServerType::GRPC => format!("gRPC server on {}:{}", self.host, self.port),
73        }
74    }
75}
76
77/// Common server traits for consistent startup behavior
78pub trait ServerStarter {
79    /// Get the server type
80    fn server_type(&self) -> ServerType;
81
82    /// Get the port this server will bind to
83    fn port(&self) -> u16;
84
85    /// Start the server (implementation-specific)
86    fn start_server(
87        self,
88    ) -> impl std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send;
89}
90
91/// Helper function to start any server that implements ServerStarter
92pub async fn start_server<S: ServerStarter>(
93    server: S,
94) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
95    let port = server.port();
96    let server_type = server.server_type();
97
98    match server_type {
99        ServerType::HTTP => tracing::info!("HTTP listening on port {}", port),
100        ServerType::WebSocket => tracing::info!("WebSocket listening on port {}", port),
101        ServerType::GRPC => tracing::info!("gRPC listening on port {}", port),
102    }
103
104    server.start_server().await
105}
106
107/// Server health check utilities
108pub mod health {
109    use serde::{Deserialize, Serialize};
110
111    #[derive(Debug, Serialize, Deserialize)]
112    pub struct HealthStatus {
113        pub status: String,
114        pub timestamp: String,
115        pub uptime_seconds: u64,
116        pub version: String,
117    }
118
119    impl HealthStatus {
120        pub fn healthy(uptime_seconds: u64, version: &str) -> Self {
121            Self {
122                status: "healthy".to_string(),
123                timestamp: chrono::Utc::now().to_rfc3339(),
124                uptime_seconds,
125                version: version.to_string(),
126            }
127        }
128
129        pub fn unhealthy(reason: &str, uptime_seconds: u64, version: &str) -> Self {
130            Self {
131                status: format!("unhealthy: {}", reason),
132                timestamp: chrono::Utc::now().to_rfc3339(),
133                uptime_seconds,
134                version: version.to_string(),
135            }
136        }
137    }
138}
139
140/// Common error response utilities
141pub mod errors {
142    use axum::{http::StatusCode, Json};
143    use serde_json::json;
144
145    /// Create a standard JSON error response
146    pub fn json_error(status: StatusCode, message: &str) -> (StatusCode, Json<serde_json::Value>) {
147        let error_response = json!({
148            "error": {
149                "message": message,
150                "status_code": status.as_u16()
151            },
152            "timestamp": chrono::Utc::now().to_rfc3339()
153        });
154
155        (status, Json(error_response))
156    }
157
158    /// Create a standard JSON success response
159    pub fn json_success<T: serde::Serialize>(data: T) -> (StatusCode, Json<serde_json::Value>) {
160        let success_response = json!({
161            "success": true,
162            "data": data,
163            "timestamp": chrono::Utc::now().to_rfc3339()
164        });
165
166        (StatusCode::OK, Json(success_response))
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_create_socket_addr() {
176        let addr = create_socket_addr("127.0.0.1", 9080).unwrap();
177        assert_eq!(addr.to_string(), "127.0.0.1:9080");
178    }
179
180    #[test]
181    fn test_server_config() {
182        let config = ServerConfig::http(3000);
183        assert_eq!(config.port, 3000);
184        assert_eq!(config.host, "0.0.0.0");
185        matches!(config.server_type, ServerType::HTTP);
186    }
187
188    #[test]
189    fn test_localhost_socket_addr() {
190        let addr = localhost_socket_addr(9080);
191        assert_eq!(addr.to_string(), "127.0.0.1:9080");
192    }
193
194    #[test]
195    fn test_wildcard_socket_addr() {
196        let addr = wildcard_socket_addr(9080);
197        assert_eq!(addr.to_string(), "0.0.0.0:9080");
198    }
199}