rustkernel_ecosystem/
common.rs

1//! Common types and utilities for ecosystem integrations
2
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7/// Service configuration shared across integrations
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ServiceConfig {
10    /// Service name
11    pub name: String,
12    /// Service version
13    pub version: String,
14    /// Listen address
15    pub address: String,
16    /// Port
17    pub port: u16,
18    /// Enable request logging
19    pub request_logging: bool,
20    /// Enable metrics
21    pub metrics_enabled: bool,
22    /// Default timeout
23    pub default_timeout: Duration,
24    /// Max request body size
25    pub max_body_size: usize,
26    /// Enable CORS
27    pub cors_enabled: bool,
28    /// CORS allowed origins
29    pub cors_origins: Vec<String>,
30}
31
32impl Default for ServiceConfig {
33    fn default() -> Self {
34        Self {
35            name: "rustkernels".to_string(),
36            version: env!("CARGO_PKG_VERSION").to_string(),
37            address: "0.0.0.0".to_string(),
38            port: 8080,
39            request_logging: true,
40            metrics_enabled: true,
41            default_timeout: Duration::from_secs(30),
42            max_body_size: 10 * 1024 * 1024, // 10MB
43            cors_enabled: true,
44            cors_origins: vec!["*".to_string()],
45        }
46    }
47}
48
49impl ServiceConfig {
50    /// Create development configuration
51    pub fn development() -> Self {
52        Self {
53            name: "rustkernels-dev".to_string(),
54            address: "127.0.0.1".to_string(),
55            request_logging: true,
56            ..Default::default()
57        }
58    }
59
60    /// Create production configuration
61    pub fn production() -> Self {
62        Self {
63            request_logging: false, // Use structured logging
64            cors_origins: vec![],   // Configure explicitly
65            ..Default::default()
66        }
67    }
68
69    /// Get the full bind address
70    pub fn bind_address(&self) -> String {
71        format!("{}:{}", self.address, self.port)
72    }
73}
74
75/// Request context passed through the service stack
76#[derive(Debug, Clone)]
77pub struct RequestContext {
78    /// Unique request ID
79    pub request_id: String,
80    /// Start time
81    pub start_time: Instant,
82    /// Trace ID for distributed tracing
83    pub trace_id: Option<String>,
84    /// Span ID
85    pub span_id: Option<String>,
86    /// Tenant ID
87    pub tenant_id: Option<String>,
88    /// User ID (if authenticated)
89    pub user_id: Option<String>,
90    /// Request path
91    pub path: String,
92    /// Request method
93    pub method: String,
94}
95
96impl RequestContext {
97    /// Create a new request context
98    pub fn new(path: impl Into<String>, method: impl Into<String>) -> Self {
99        Self {
100            request_id: uuid::Uuid::new_v4().to_string(),
101            start_time: Instant::now(),
102            trace_id: None,
103            span_id: None,
104            tenant_id: None,
105            user_id: None,
106            path: path.into(),
107            method: method.into(),
108        }
109    }
110
111    /// Set trace ID from header
112    pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
113        self.trace_id = Some(trace_id.into());
114        self
115    }
116
117    /// Set tenant ID
118    pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
119        self.tenant_id = Some(tenant_id.into());
120        self
121    }
122
123    /// Set user ID
124    pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
125        self.user_id = Some(user_id.into());
126        self
127    }
128
129    /// Get elapsed time since request start
130    pub fn elapsed(&self) -> Duration {
131        self.start_time.elapsed()
132    }
133
134    /// Get elapsed time in microseconds
135    pub fn elapsed_us(&self) -> u64 {
136        self.start_time.elapsed().as_micros() as u64
137    }
138}
139
140/// Rate limiter configuration
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct RateLimitConfig {
143    /// Enable rate limiting
144    pub enabled: bool,
145    /// Requests per second
146    pub requests_per_second: u32,
147    /// Burst size
148    pub burst_size: u32,
149    /// Per-tenant rate limiting
150    pub per_tenant: bool,
151}
152
153impl Default for RateLimitConfig {
154    fn default() -> Self {
155        Self {
156            enabled: true,
157            requests_per_second: 100,
158            burst_size: 200,
159            per_tenant: true,
160        }
161    }
162}
163
164/// Metrics collector for service endpoints
165pub struct ServiceMetrics {
166    /// Total requests
167    pub total_requests: std::sync::atomic::AtomicU64,
168    /// Total errors
169    pub total_errors: std::sync::atomic::AtomicU64,
170    /// Total latency (microseconds)
171    pub total_latency_us: std::sync::atomic::AtomicU64,
172}
173
174impl ServiceMetrics {
175    /// Create new metrics
176    pub fn new() -> Arc<Self> {
177        Arc::new(Self {
178            total_requests: std::sync::atomic::AtomicU64::new(0),
179            total_errors: std::sync::atomic::AtomicU64::new(0),
180            total_latency_us: std::sync::atomic::AtomicU64::new(0),
181        })
182    }
183
184    /// Record a request
185    pub fn record_request(&self, latency_us: u64, is_error: bool) {
186        use std::sync::atomic::Ordering;
187        self.total_requests.fetch_add(1, Ordering::Relaxed);
188        self.total_latency_us
189            .fetch_add(latency_us, Ordering::Relaxed);
190        if is_error {
191            self.total_errors.fetch_add(1, Ordering::Relaxed);
192        }
193    }
194
195    /// Get request count
196    pub fn request_count(&self) -> u64 {
197        self.total_requests
198            .load(std::sync::atomic::Ordering::Relaxed)
199    }
200
201    /// Get error count
202    pub fn error_count(&self) -> u64 {
203        self.total_errors.load(std::sync::atomic::Ordering::Relaxed)
204    }
205
206    /// Get average latency in microseconds
207    pub fn avg_latency_us(&self) -> f64 {
208        use std::sync::atomic::Ordering;
209        let total = self.total_latency_us.load(Ordering::Relaxed) as f64;
210        let count = self.total_requests.load(Ordering::Relaxed) as f64;
211        if count > 0.0 { total / count } else { 0.0 }
212    }
213}
214
215impl Default for ServiceMetrics {
216    fn default() -> Self {
217        Self {
218            total_requests: std::sync::atomic::AtomicU64::new(0),
219            total_errors: std::sync::atomic::AtomicU64::new(0),
220            total_latency_us: std::sync::atomic::AtomicU64::new(0),
221        }
222    }
223}
224
225/// Standard API paths
226pub mod paths {
227    /// Health check endpoint
228    pub const HEALTH: &str = "/health";
229    /// Liveness probe
230    pub const LIVENESS: &str = "/health/live";
231    /// Readiness probe
232    pub const READINESS: &str = "/health/ready";
233    /// Metrics endpoint
234    pub const METRICS: &str = "/metrics";
235    /// Kernel execution endpoint
236    pub const KERNEL_EXECUTE: &str = "/api/v1/kernels/:kernel_id/execute";
237    /// List kernels endpoint
238    pub const KERNEL_LIST: &str = "/api/v1/kernels";
239    /// Get kernel info endpoint
240    pub const KERNEL_INFO: &str = "/api/v1/kernels/:kernel_id";
241}
242
243/// Standard HTTP headers
244pub mod headers {
245    /// Request ID header
246    pub const X_REQUEST_ID: &str = "X-Request-ID";
247    /// Trace ID header (W3C)
248    pub const TRACEPARENT: &str = "traceparent";
249    /// Tenant ID header
250    pub const X_TENANT_ID: &str = "X-Tenant-ID";
251    /// API key header
252    pub const X_API_KEY: &str = "X-API-Key";
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_service_config() {
261        let config = ServiceConfig::default();
262        assert_eq!(config.bind_address(), "0.0.0.0:8080");
263    }
264
265    #[test]
266    fn test_request_context() {
267        let ctx = RequestContext::new("/api/v1/kernels", "POST")
268            .with_tenant_id("tenant-123")
269            .with_user_id("user-456");
270
271        assert!(!ctx.request_id.is_empty());
272        assert_eq!(ctx.tenant_id, Some("tenant-123".to_string()));
273        assert_eq!(ctx.user_id, Some("user-456".to_string()));
274    }
275
276    #[test]
277    fn test_service_metrics() {
278        let metrics = ServiceMetrics::new();
279
280        metrics.record_request(1000, false);
281        metrics.record_request(2000, false);
282        metrics.record_request(3000, true);
283
284        assert_eq!(metrics.request_count(), 3);
285        assert_eq!(metrics.error_count(), 1);
286        assert!((metrics.avg_latency_us() - 2000.0).abs() < 0.1);
287    }
288}