1use crate::middleware::{AuthConfig, CompressionConfig, CorsConfig};
6use chrono::{DateTime, Utc};
7use ferrum_types::RequestId;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::PathBuf;
11use std::time::{Duration, Instant};
12
13#[derive(Debug, Clone)]
15pub struct HttpRequest {
16 pub method: HttpMethod,
18
19 pub path: String,
21
22 pub query: HashMap<String, String>,
24
25 pub headers: Headers,
27
28 pub body: Vec<u8>,
30
31 pub client_ip: Option<std::net::IpAddr>,
33
34 pub timestamp: DateTime<Utc>,
36
37 pub request_id: RequestId,
39}
40
41#[derive(Debug, Clone)]
43pub struct HttpResponse {
44 pub status: StatusCode,
46
47 pub headers: Headers,
49
50 pub body: Vec<u8>,
52
53 pub content_type: String,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
59pub enum HttpMethod {
60 GET,
61 POST,
62 PUT,
63 DELETE,
64 PATCH,
65 HEAD,
66 OPTIONS,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71pub enum StatusCode {
72 OK = 200,
73 Created = 201,
74 NoContent = 204,
75 BadRequest = 400,
76 Unauthorized = 401,
77 Forbidden = 403,
78 NotFound = 404,
79 MethodNotAllowed = 405,
80 TooManyRequests = 429,
81 InternalServerError = 500,
82 BadGateway = 502,
83 ServiceUnavailable = 503,
84 GatewayTimeout = 504,
85}
86
87pub type Headers = HashMap<String, String>;
89
90#[derive(Debug, Clone)]
92pub struct RequestContext {
93 pub client_info: Option<ClientInfo>,
95
96 pub auth_result: Option<AuthResult>,
98
99 pub start_time: Instant,
101
102 pub data: HashMap<String, serde_json::Value>,
104
105 pub trace_id: String,
107}
108
109impl Default for RequestContext {
110 fn default() -> Self {
111 Self {
112 client_info: None,
113 auth_result: None,
114 start_time: Instant::now(),
115 data: HashMap::new(),
116 trace_id: uuid::Uuid::new_v4().to_string(),
117 }
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ServerConfig {
124 pub host: String,
126
127 pub port: u16,
129
130 pub max_connections: usize,
132
133 pub request_timeout: Duration,
135
136 pub keep_alive_timeout: Duration,
138
139 pub enable_tls: bool,
141
142 pub tls_cert_path: Option<String>,
144
145 pub tls_key_path: Option<String>,
147
148 pub cors: Option<CorsConfig>,
150
151 pub compression: Option<CompressionConfig>,
153
154 pub auth: Option<AuthConfig>,
156
157 pub api_version: ApiVersion,
159
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub request_dump_dir: Option<PathBuf>,
163
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub profile_jsonl: Option<PathBuf>,
167
168 #[serde(default)]
170 pub profile_detail: ferrum_types::ObservabilityProfileDetail,
171
172 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub memory_profile_jsonl: Option<PathBuf>,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179pub enum ApiVersion {
180 V1,
181 V2,
182}
183
184#[derive(Debug, Clone, Default, Serialize, Deserialize)]
186pub struct ServerMetrics {
187 pub total_requests: u64,
189
190 pub requests_by_endpoint: HashMap<String, u64>,
192
193 pub requests_by_status: HashMap<u16, u64>,
195
196 pub avg_response_time_ms: f64,
198
199 pub p95_response_time_ms: f64,
201
202 pub p99_response_time_ms: f64,
204
205 pub active_connections: usize,
207
208 pub bytes_sent: u64,
210
211 pub bytes_received: u64,
213
214 pub error_rate: f32,
216
217 pub uptime_seconds: u64,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
223pub enum HealthStatus {
224 Healthy,
225 Degraded,
226 Unhealthy,
227 Unknown,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct ComponentHealth {
233 pub name: String,
234 pub status: HealthStatus,
235 pub message: Option<String>,
236 pub last_check: DateTime<Utc>,
237 pub response_time_ms: Option<u64>,
238}
239
240#[derive(Debug, Clone)]
242pub struct AuthResult {
243 pub success: bool,
244 pub client_info: Option<ClientInfo>,
245 pub token_claims: Option<TokenClaims>,
246 pub error: Option<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct ClientInfo {
252 pub client_id: String,
253 pub api_key: Option<String>,
254 pub organization_id: Option<String>,
255 pub rate_limit_tier: RateLimitTier,
256 pub permissions: Vec<String>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct TokenClaims {
262 pub sub: String,
263 pub exp: u64,
264 pub iat: u64,
265 pub iss: String,
266 pub aud: String,
267 pub permissions: Vec<String>,
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
272pub enum AuthScheme {
273 ApiKey,
274 Bearer,
275 Basic,
276 Custom(String),
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
281pub enum RateLimitTier {
282 Free,
283 Pro,
284 Enterprise,
285 Custom(String),
286}
287
288#[derive(Debug, Clone)]
290pub struct RateLimitResult {
291 pub allowed: bool,
292 pub limit: u32,
293 pub remaining: u32,
294 pub reset_time: DateTime<Utc>,
295 pub retry_after: Option<Duration>,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct RateLimitStatus {
301 pub requests_per_minute: u32,
302 pub requests_per_hour: u32,
303 pub tokens_per_minute: u32,
304 pub current_usage: RateLimitUsage,
305 pub reset_times: RateLimitResetTimes,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct RateLimitUsage {
311 pub requests_this_minute: u32,
312 pub requests_this_hour: u32,
313 pub tokens_this_minute: u32,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct RateLimitResetTimes {
319 pub requests_reset_minute: DateTime<Utc>,
320 pub requests_reset_hour: DateTime<Utc>,
321 pub tokens_reset_minute: DateTime<Utc>,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct ValidationRules {
327 pub max_prompt_length: usize,
329
330 pub max_completion_tokens: usize,
332
333 pub allowed_models: Option<Vec<String>>,
335
336 pub required_params: Vec<String>,
338
339 pub param_constraints: HashMap<String, ParameterConstraint>,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub enum ParameterConstraint {
346 Range { min: f64, max: f64 },
347 OneOf(Vec<serde_json::Value>),
348 Regex(String),
349 Custom(String),
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct HealthCheckConfig {
355 pub interval: Duration,
357
358 pub components: Vec<String>,
360
361 pub timeout: Duration,
363
364 pub retries: u32,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct StreamConfig {
371 pub chunk_size: usize,
373
374 pub buffer_size: usize,
376
377 pub flush_interval: Duration,
379
380 pub enable_compression: bool,
382
383 pub timeout: Duration,
385}
386
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub enum ShutdownSignal {
390 SIGTERM,
391 SIGINT,
392 SIGQUIT,
393 Custom,
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
398pub enum LifecycleState {
399 Starting,
400 Running,
401 Stopping,
402 Stopped,
403 Error,
404}
405
406impl Default for ServerConfig {
407 fn default() -> Self {
408 Self {
409 host: "0.0.0.0".to_string(),
410 port: 8000,
411 max_connections: 10000,
412 request_timeout: Duration::from_secs(30),
413 keep_alive_timeout: Duration::from_secs(60),
414 enable_tls: false,
415 tls_cert_path: None,
416 tls_key_path: None,
417 cors: None,
418 compression: None,
419 auth: None,
420 api_version: ApiVersion::V1,
421 request_dump_dir: None,
422 profile_jsonl: None,
423 profile_detail: ferrum_types::ObservabilityProfileDetail::Off,
424 memory_profile_jsonl: None,
425 }
426 }
427}
428
429impl Default for StreamConfig {
430 fn default() -> Self {
431 Self {
432 chunk_size: 1024,
433 buffer_size: 8192,
434 flush_interval: Duration::from_millis(50),
435 enable_compression: false,
436 timeout: Duration::from_secs(300),
437 }
438 }
439}
440
441impl Default for HealthCheckConfig {
442 fn default() -> Self {
443 Self {
444 interval: Duration::from_secs(30),
445 components: vec![
446 "inference_engine".to_string(),
447 "scheduler".to_string(),
448 "cache".to_string(),
449 ],
450 timeout: Duration::from_secs(5),
451 retries: 3,
452 }
453 }
454}
455
456#[cfg(test)]
461mod tests {
462 use super::*;
463
464 #[test]
465 fn test_http_method_eq() {
466 assert_eq!(HttpMethod::GET, HttpMethod::GET);
467 assert_ne!(HttpMethod::GET, HttpMethod::POST);
468 }
469
470 #[test]
471 fn test_http_method_clone() {
472 let method = HttpMethod::POST;
473 let cloned = method.clone();
474 assert_eq!(method, cloned);
475 }
476
477 #[test]
478 fn test_http_method_debug() {
479 let method = HttpMethod::GET;
480 let debug_str = format!("{:?}", method);
481 assert!(debug_str.contains("GET"));
482 }
483
484 #[test]
485 fn test_status_code_values() {
486 assert_eq!(StatusCode::OK as u16, 200);
487 assert_eq!(StatusCode::BadRequest as u16, 400);
488 assert_eq!(StatusCode::InternalServerError as u16, 500);
489 }
490
491 #[test]
492 fn test_status_code_eq() {
493 assert_eq!(StatusCode::OK, StatusCode::OK);
494 assert_ne!(StatusCode::OK, StatusCode::Created);
495 }
496
497 #[test]
498 fn test_http_request_creation() {
499 let request = HttpRequest {
500 method: HttpMethod::GET,
501 path: "/api/v1/test".to_string(),
502 query: HashMap::new(),
503 headers: HashMap::new(),
504 body: vec![],
505 client_ip: None,
506 timestamp: Utc::now(),
507 request_id: RequestId::new(),
508 };
509
510 assert_eq!(request.method, HttpMethod::GET);
511 assert_eq!(request.path, "/api/v1/test");
512 }
513
514 #[test]
515 fn test_http_request_clone() {
516 let request = HttpRequest {
517 method: HttpMethod::POST,
518 path: "/test".to_string(),
519 query: HashMap::new(),
520 headers: HashMap::new(),
521 body: b"test body".to_vec(),
522 client_ip: None,
523 timestamp: Utc::now(),
524 request_id: RequestId::new(),
525 };
526
527 let cloned = request.clone();
528 assert_eq!(request.method, cloned.method);
529 assert_eq!(request.body, cloned.body);
530 }
531
532 #[test]
533 fn test_http_response_creation() {
534 let response = HttpResponse {
535 status: StatusCode::OK,
536 headers: HashMap::new(),
537 body: b"success".to_vec(),
538 content_type: "application/json".to_string(),
539 };
540
541 assert_eq!(response.status, StatusCode::OK);
542 assert_eq!(response.content_type, "application/json");
543 }
544
545 #[test]
546 fn test_request_context_default() {
547 let context = RequestContext {
548 client_info: None,
549 auth_result: None,
550 start_time: Instant::now(),
551 data: HashMap::new(),
552 trace_id: "test-trace".to_string(),
553 };
554
555 assert!(context.client_info.is_none());
556 assert_eq!(context.trace_id, "test-trace");
557 }
558
559 #[test]
560 fn test_server_config_default() {
561 let config = ServerConfig::default();
562
563 assert_eq!(config.host, "0.0.0.0");
564 assert_eq!(config.port, 8000); }
566
567 #[test]
568 fn test_server_metrics_default() {
569 let metrics = ServerMetrics::default();
570
571 assert_eq!(metrics.total_requests, 0);
572 }
574
575 #[test]
576 fn test_health_status_creation() {
577 let _status = HealthStatus::Healthy;
579 let _status2 = HealthStatus::Degraded;
580 assert!(true);
581 }
582
583 #[test]
584 fn test_api_version_eq() {
585 assert_eq!(ApiVersion::V1, ApiVersion::V1);
586 assert_ne!(ApiVersion::V1, ApiVersion::V2);
587 }
588
589 #[test]
590 fn test_api_version_eq_only() {
591 assert_eq!(ApiVersion::V1, ApiVersion::V1);
593 assert_ne!(ApiVersion::V1, ApiVersion::V2);
594 }
595
596 #[test]
597 fn test_component_health_healthy() {
598 let health = ComponentHealth {
599 name: "test".to_string(),
600 status: HealthStatus::Healthy,
601 message: Some("OK".to_string()),
602 last_check: Utc::now(),
603 response_time_ms: Some(10),
604 };
605
606 assert_eq!(health.status, HealthStatus::Healthy);
607 assert!(health.message.is_some());
608 }
609
610 #[test]
611 fn test_component_health_unhealthy() {
612 let health = ComponentHealth {
613 name: "test".to_string(),
614 status: HealthStatus::Unhealthy,
615 message: Some("Connection failed".to_string()),
616 last_check: Utc::now(),
617 response_time_ms: None,
618 };
619
620 assert_eq!(health.status, HealthStatus::Unhealthy);
621 assert!(health.message.is_some());
622 }
623
624 #[test]
625 fn test_client_info_creation() {
626 let info = ClientInfo {
627 client_id: "client123".to_string(),
628 api_key: Some("key123".to_string()),
629 organization_id: None,
630 rate_limit_tier: RateLimitTier::Free,
631 permissions: vec!["read".to_string()],
632 };
633
634 assert_eq!(info.client_id, "client123");
635 assert!(info.api_key.is_some());
636 }
637
638 #[test]
639 fn test_auth_result_success() {
640 let result = AuthResult {
641 success: true,
642 client_info: None,
643 token_claims: None,
644 error: None,
645 };
646
647 assert!(result.success);
648 assert!(result.error.is_none());
649 }
650
651 #[test]
652 fn test_health_check_config_default() {
653 let config = HealthCheckConfig::default();
654
655 assert_eq!(config.interval, Duration::from_secs(30));
656 assert_eq!(config.timeout, Duration::from_secs(5));
657 assert_eq!(config.retries, 3);
658 assert!(config.components.len() >= 3);
659 }
660
661 #[test]
662 fn test_server_config_clone() {
663 let config = ServerConfig::default();
664 let cloned = config.clone();
665
666 assert_eq!(config.host, cloned.host);
667 assert_eq!(config.port, cloned.port);
668 }
669
670 #[test]
671 fn test_server_metrics_clone() {
672 let metrics = ServerMetrics::default();
673 let cloned = metrics.clone();
674
675 assert_eq!(metrics.total_requests, cloned.total_requests);
676 }
677
678 #[test]
679 fn test_http_request_with_headers() {
680 let mut headers = HashMap::new();
681 headers.insert("Content-Type".to_string(), "application/json".to_string());
682 headers.insert("Authorization".to_string(), "Bearer token".to_string());
683
684 let request = HttpRequest {
685 method: HttpMethod::POST,
686 path: "/api/v1/completions".to_string(),
687 query: HashMap::new(),
688 headers,
689 body: vec![],
690 client_ip: None,
691 timestamp: Utc::now(),
692 request_id: RequestId::new(),
693 };
694
695 assert_eq!(request.headers.len(), 2);
696 assert!(request.headers.contains_key("Content-Type"));
697 }
698
699 #[test]
700 fn test_http_response_with_body() {
701 let body = serde_json::json!({"message": "success"}).to_string();
702
703 let response = HttpResponse {
704 status: StatusCode::OK,
705 headers: HashMap::new(),
706 body: body.as_bytes().to_vec(),
707 content_type: "application/json".to_string(),
708 };
709
710 assert!(!response.body.is_empty());
711 assert_eq!(response.status, StatusCode::OK);
712 }
713}