1use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5use std::time::{Duration, Instant};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ServiceConfig {
10 pub name: String,
12 pub version: String,
14 pub address: String,
16 pub port: u16,
18 pub request_logging: bool,
20 pub metrics_enabled: bool,
22 pub default_timeout: Duration,
24 pub max_body_size: usize,
26 pub cors_enabled: bool,
28 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, cors_enabled: true,
44 cors_origins: vec!["*".to_string()],
45 }
46 }
47}
48
49impl ServiceConfig {
50 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 pub fn production() -> Self {
62 Self {
63 request_logging: false, cors_origins: vec![], ..Default::default()
66 }
67 }
68
69 pub fn bind_address(&self) -> String {
71 format!("{}:{}", self.address, self.port)
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct RequestContext {
78 pub request_id: String,
80 pub start_time: Instant,
82 pub trace_id: Option<String>,
84 pub span_id: Option<String>,
86 pub tenant_id: Option<String>,
88 pub user_id: Option<String>,
90 pub path: String,
92 pub method: String,
94}
95
96impl RequestContext {
97 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 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 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 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 pub fn elapsed(&self) -> Duration {
131 self.start_time.elapsed()
132 }
133
134 pub fn elapsed_us(&self) -> u64 {
136 self.start_time.elapsed().as_micros() as u64
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct RateLimitConfig {
143 pub enabled: bool,
145 pub requests_per_second: u32,
147 pub burst_size: u32,
149 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
164pub struct ServiceMetrics {
166 total_requests: std::sync::atomic::AtomicU64,
168 total_errors: std::sync::atomic::AtomicU64,
170 total_latency_us: std::sync::atomic::AtomicU64,
172 min_latency_us: std::sync::atomic::AtomicU64,
174 max_latency_us: std::sync::atomic::AtomicU64,
176}
177
178impl ServiceMetrics {
179 pub fn new() -> Arc<Self> {
181 Arc::new(Self {
182 total_requests: std::sync::atomic::AtomicU64::new(0),
183 total_errors: std::sync::atomic::AtomicU64::new(0),
184 total_latency_us: std::sync::atomic::AtomicU64::new(0),
185 min_latency_us: std::sync::atomic::AtomicU64::new(u64::MAX),
186 max_latency_us: std::sync::atomic::AtomicU64::new(0),
187 })
188 }
189
190 pub fn record_request(&self, latency_us: u64, is_error: bool) {
192 use std::sync::atomic::Ordering;
193 self.total_requests.fetch_add(1, Ordering::Relaxed);
194 self.total_latency_us
195 .fetch_add(latency_us, Ordering::Relaxed);
196 if is_error {
197 self.total_errors.fetch_add(1, Ordering::Relaxed);
198 }
199 self.min_latency_us.fetch_min(latency_us, Ordering::Relaxed);
201 self.max_latency_us.fetch_max(latency_us, Ordering::Relaxed);
203 }
204
205 pub fn request_count(&self) -> u64 {
207 self.total_requests
208 .load(std::sync::atomic::Ordering::Relaxed)
209 }
210
211 pub fn error_count(&self) -> u64 {
213 self.total_errors.load(std::sync::atomic::Ordering::Relaxed)
214 }
215
216 pub fn avg_latency_us(&self) -> f64 {
218 use std::sync::atomic::Ordering;
219 let total = self.total_latency_us.load(Ordering::Relaxed) as f64;
220 let count = self.total_requests.load(Ordering::Relaxed) as f64;
221 if count > 0.0 { total / count } else { 0.0 }
222 }
223
224 pub fn min_latency_us(&self) -> u64 {
226 let val = self
227 .min_latency_us
228 .load(std::sync::atomic::Ordering::Relaxed);
229 if val == u64::MAX { 0 } else { val }
230 }
231
232 pub fn max_latency_us(&self) -> u64 {
234 self.max_latency_us
235 .load(std::sync::atomic::Ordering::Relaxed)
236 }
237}
238
239impl Default for ServiceMetrics {
240 fn default() -> Self {
241 Self {
242 total_requests: std::sync::atomic::AtomicU64::new(0),
243 total_errors: std::sync::atomic::AtomicU64::new(0),
244 total_latency_us: std::sync::atomic::AtomicU64::new(0),
245 min_latency_us: std::sync::atomic::AtomicU64::new(u64::MAX),
246 max_latency_us: std::sync::atomic::AtomicU64::new(0),
247 }
248 }
249}
250
251pub mod paths {
253 pub const HEALTH: &str = "/health";
255 pub const LIVENESS: &str = "/health/live";
257 pub const READINESS: &str = "/health/ready";
259 pub const METRICS: &str = "/metrics";
261 pub const KERNEL_EXECUTE: &str = "/api/v1/kernels/:kernel_id/execute";
263 pub const KERNEL_LIST: &str = "/api/v1/kernels";
265 pub const KERNEL_INFO: &str = "/api/v1/kernels/:kernel_id";
267}
268
269pub mod headers {
271 pub const X_REQUEST_ID: &str = "X-Request-ID";
273 pub const TRACEPARENT: &str = "traceparent";
275 pub const X_TENANT_ID: &str = "X-Tenant-ID";
277 pub const X_API_KEY: &str = "X-API-Key";
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_service_config() {
287 let config = ServiceConfig::default();
288 assert_eq!(config.bind_address(), "0.0.0.0:8080");
289 }
290
291 #[test]
292 fn test_request_context() {
293 let ctx = RequestContext::new("/api/v1/kernels", "POST")
294 .with_tenant_id("tenant-123")
295 .with_user_id("user-456");
296
297 assert!(!ctx.request_id.is_empty());
298 assert_eq!(ctx.tenant_id, Some("tenant-123".to_string()));
299 assert_eq!(ctx.user_id, Some("user-456".to_string()));
300 }
301
302 #[test]
303 fn test_service_metrics() {
304 let metrics = ServiceMetrics::new();
305
306 assert_eq!(metrics.min_latency_us(), 0);
308 assert_eq!(metrics.max_latency_us(), 0);
309
310 metrics.record_request(1000, false);
311 metrics.record_request(2000, false);
312 metrics.record_request(3000, true);
313
314 assert_eq!(metrics.request_count(), 3);
315 assert_eq!(metrics.error_count(), 1);
316 assert!((metrics.avg_latency_us() - 2000.0).abs() < 0.1);
317 assert_eq!(metrics.min_latency_us(), 1000);
318 assert_eq!(metrics.max_latency_us(), 3000);
319 }
320}