kode_bridge/
lib.rs

1pub mod buffer_pool;
2pub mod config;
3pub mod errors;
4pub mod http_client;
5pub mod metrics;
6pub mod parser_cache;
7pub mod pool;
8pub mod response;
9pub mod retry;
10
11#[cfg(feature = "client")]
12pub mod ipc_http_client;
13
14#[cfg(feature = "client")]
15pub mod ipc_stream_client;
16
17#[cfg(feature = "client")]
18pub mod stream_client;
19
20#[cfg(feature = "server")]
21pub mod ipc_http_server;
22
23#[cfg(feature = "server")]
24pub mod ipc_stream_server;
25
26pub use config::*;
27pub use errors::*;
28pub use metrics::{
29    global_metrics, init_metrics, BufferPoolStats, HealthChecker, HealthReport, HealthStatus,
30    MetricsCollector, MetricsSnapshot, ParserCacheStats,
31};
32pub use response::*;
33
34#[cfg(feature = "client")]
35pub use ipc_http_client::*;
36
37#[cfg(feature = "client")]
38pub use ipc_stream_client::*;
39
40#[cfg(feature = "client")]
41pub use stream_client::*;
42
43#[cfg(feature = "server")]
44pub use ipc_http_server::{IpcHttpServer, RequestContext, Router, ServerConfig};
45
46#[cfg(feature = "server")]
47pub use ipc_stream_server::*;
48
49#[cfg(test)]
50mod test_utils {
51    use crate::config::GlobalConfig;
52    use tokio::sync::oneshot;
53
54    pub fn test_config() -> GlobalConfig {
55        let mut config = GlobalConfig::default();
56        config.client.default_timeout_ms = 5000;
57        config.client.max_retries = 2;
58        config.client.retry_delay_ms = 100;
59        config.client.connection_timeout_ms = 1000;
60        config.client.pool.max_size = 5;
61        config.client.pool.min_idle = 2; // 确保 min_idle <= max_size
62        config
63    }
64
65    pub async fn _setup_test_server(
66        _socket_path: &str,
67    ) -> (tokio::task::JoinHandle<()>, oneshot::Sender<()>) {
68        // TODO: Fix server API integration
69        let (shutdown_tx, _shutdown_rx) = oneshot::channel();
70        let handle = tokio::spawn(async move {
71            // Placeholder for server implementation
72        });
73        (handle, shutdown_tx)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use test_utils::*;
81
82    #[test]
83    fn test_config_validation() {
84        let mut invalid_config = test_config();
85        invalid_config.client.default_timeout_ms = 0;
86
87        assert!(invalid_config.validate().is_err());
88
89        let valid_config = test_config();
90        assert!(valid_config.validate().is_ok());
91    }
92
93    #[test]
94    fn test_config_builder() {
95        use crate::config::ConfigBuilder;
96        use std::time::Duration;
97
98        let config = ConfigBuilder::new()
99            .client_timeout(Duration::from_millis(3000))
100            .max_retries(3)
101            .enable_logging("debug")
102            .enable_feature("caching")
103            .build()
104            .unwrap();
105
106        assert_eq!(config.client.default_timeout_ms, 3000);
107        assert_eq!(config.client.max_retries, 3);
108        assert_eq!(config.logging.level, "debug");
109        assert!(config.features.caching);
110    }
111
112    #[test]
113    fn test_error_categorization() {
114        use crate::errors::KodeBridgeError;
115
116        let connection_error = KodeBridgeError::Connection {
117            message: "Connection failed".to_string(),
118        };
119        assert!(connection_error.is_retriable());
120        assert!(!connection_error.is_client_error());
121        assert!(!connection_error.is_server_error());
122
123        let config_error = KodeBridgeError::Configuration {
124            message: "Invalid configuration".to_string(),
125        };
126        assert!(!config_error.is_retriable());
127
128        let client_error = KodeBridgeError::ClientError { status: 400 };
129        assert!(client_error.is_client_error());
130        assert!(!client_error.is_server_error());
131
132        let server_error = KodeBridgeError::ServerError { status: 500 };
133        assert!(server_error.is_server_error());
134        assert!(!server_error.is_client_error());
135    }
136
137    #[test]
138    fn test_timeout_error() {
139        use crate::errors::KodeBridgeError;
140
141        let timeout_error = KodeBridgeError::timeout(5000);
142        assert!(timeout_error.is_retriable());
143
144        match timeout_error {
145            KodeBridgeError::Timeout { duration_ms } => {
146                assert_eq!(duration_ms, 5000);
147            }
148            _ => panic!("Expected timeout error"),
149        }
150    }
151
152    #[test]
153    fn test_error_construction_helpers() {
154        use crate::errors::KodeBridgeError;
155
156        let conn_err = KodeBridgeError::connection("Test connection error");
157        assert!(
158            matches!(conn_err, KodeBridgeError::Connection { message } if message == "Test connection error")
159        );
160
161        let proto_err = KodeBridgeError::protocol("Protocol violation");
162        assert!(
163            matches!(proto_err, KodeBridgeError::Protocol { message } if message == "Protocol violation")
164        );
165
166        let config_err = KodeBridgeError::configuration("Bad config");
167        assert!(
168            matches!(config_err, KodeBridgeError::Configuration { message } if message == "Bad config")
169        );
170
171        let custom_err = KodeBridgeError::custom("Custom error message");
172        assert!(
173            matches!(custom_err, KodeBridgeError::Custom { message } if message == "Custom error message")
174        );
175    }
176
177    #[test]
178    fn test_pool_config() {
179        use crate::pool::PoolConfig;
180
181        let pool_config = PoolConfig {
182            max_size: 10,
183            min_idle: 2,
184            max_idle_time_ms: 300_000,
185            connection_timeout_ms: 30_000,
186            retry_delay_ms: 100,
187            max_retries: 3,
188            max_concurrent_requests: 8,
189            max_requests_per_second: Some(10.0),
190        };
191
192        assert_eq!(pool_config.max_size, 10);
193        assert_eq!(pool_config.min_idle, 2);
194        assert_eq!(pool_config.max_idle_time_ms, 300_000);
195
196        let default_config = PoolConfig::default();
197        assert_eq!(default_config.max_size, 64); // 更新为新的默认值
198        assert_eq!(default_config.min_idle, 8); // 更新为新的默认值
199    }
200
201    #[cfg(feature = "server")]
202    #[test]
203    fn test_path_security() {
204        use crate::ipc_http_server::Router;
205
206        let router = Router::new();
207
208        // Test safe paths
209        assert!(router.is_safe_path("/api/users"));
210        assert!(router.is_safe_path("/"));
211        assert!(router.is_safe_path("/data/file.json"));
212
213        // Test unsafe paths - directory traversal
214        assert!(!router.is_safe_path("/../etc/passwd"));
215        assert!(!router.is_safe_path("/api/../../../etc/passwd"));
216        assert!(!router.is_safe_path("/data\\..\\windows"));
217
218        // Test paths with invalid characters
219        assert!(!router.is_safe_path("/api/users\0"));
220        assert!(!router.is_safe_path("/api/\x01users"));
221
222        // Test paths not starting with /
223        assert!(!router.is_safe_path("api/users"));
224        assert!(!router.is_safe_path("../etc/passwd"));
225
226        // Test excessively long paths
227        let long_path = "/".to_string() + &"a".repeat(3000);
228        assert!(!router.is_safe_path(&long_path));
229    }
230
231    #[cfg(feature = "server")]
232    #[test]
233    fn test_url_decoding_security() {
234        use crate::ipc_http_server::urlencoding;
235
236        // Test normal URL decoding
237        assert_eq!(urlencoding::decode("hello%20world").unwrap(), "hello world");
238        assert_eq!(urlencoding::decode("hello+world").unwrap(), "hello world");
239        assert_eq!(urlencoding::decode("hello").unwrap(), "hello");
240
241        // Test invalid hex sequences
242        assert!(urlencoding::decode("hello%GG").is_err());
243        assert!(urlencoding::decode("hello%2").is_err());
244
245        // Test non-UTF8 sequences should be rejected
246        // %C0%80 is an overlong encoding of null byte
247        assert!(urlencoding::decode("%C0%80").is_err());
248
249        // Valid UTF-8 sequences should work
250        assert_eq!(urlencoding::decode("%C3%A9").unwrap(), "é"); // é in UTF-8
251    }
252
253    #[test]
254    fn test_metrics_integration() {
255        use crate::metrics::global_metrics;
256
257        let metrics = global_metrics();
258
259        // Test request tracking
260        {
261            let tracker = metrics.request_start("GET");
262            std::thread::sleep(std::time::Duration::from_millis(1));
263            tracker.success(200);
264        }
265
266        let snapshot = metrics.snapshot();
267        assert_eq!(snapshot.total_requests, 1);
268        assert_eq!(snapshot.successful_requests, 1);
269        assert_eq!(snapshot.active_requests, 0);
270
271        // Test connection tracking
272        metrics.connection_created(true);
273        metrics.connection_created(false);
274
275        let snapshot = metrics.snapshot();
276        assert_eq!(snapshot.total_connections, 2);
277        assert_eq!(snapshot.pool_hits, 1);
278        assert_eq!(snapshot.pool_misses, 1);
279    }
280
281    #[test]
282    fn test_health_checker() {
283        use crate::metrics::{HealthChecker, HealthStatus, MetricsCollector};
284        use std::sync::Arc;
285
286        let metrics = Arc::new(MetricsCollector::new());
287        let health_checker = HealthChecker::new(metrics);
288
289        let report = health_checker.check_health();
290        assert_eq!(report.status, HealthStatus::Healthy);
291        assert!(report.issues.is_empty());
292    }
293
294    #[test]
295    fn test_buffer_pool_integration() {
296        use crate::buffer_pool::global_pools;
297        use crate::metrics::global_metrics;
298
299        let pools = global_pools();
300        let metrics = global_metrics();
301
302        // Get some buffers to test pool usage
303        let _buf1 = pools.get_small();
304        let _buf2 = pools.get_medium();
305        let _buf3 = pools.get_large();
306
307        let stats = pools.stats();
308        metrics.update_buffer_pool_stats(crate::metrics::BufferPoolStats {
309            small_pool_size: stats.small_pool_size,
310            medium_pool_size: stats.medium_pool_size,
311            large_pool_size: stats.large_pool_size,
312            total_allocations: 3,
313            total_reuses: 0,
314        });
315
316        let snapshot = metrics.snapshot();
317        assert_eq!(snapshot.buffer_pool_stats.total_allocations, 3);
318    }
319}