Skip to main content

oxirs_embed/api/
config.rs

1//! API configuration and state management
2//!
3//! This module contains configuration structures and server state management
4//! for the embedding API service.
5
6#[cfg(feature = "api-server")]
7use crate::{CacheManager, EmbeddingModel, ModelRegistry};
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Arc;
11use std::time::Duration;
12use tokio::sync::RwLock;
13use uuid::Uuid;
14
15/// Live, aggregate request metrics for the embedding API service.
16///
17/// These counters are updated by request handlers (via [`ApiMetrics::record`])
18/// and read back by the health/info endpoints. They replace the previously
19/// hard-coded placeholder values so that reported latency/error figures reflect
20/// real observed traffic. Before any request is recorded the derived rates are
21/// `0.0`, which is their true value rather than a fabricated constant.
22#[derive(Debug, Default)]
23pub struct ApiMetrics {
24    total_requests: AtomicU64,
25    total_errors: AtomicU64,
26    total_latency_us: AtomicU64,
27}
28
29impl ApiMetrics {
30    /// Create an empty metrics tracker.
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Record a completed request with its wall-clock latency and outcome.
36    pub fn record(&self, latency: Duration, is_error: bool) {
37        self.total_requests.fetch_add(1, Ordering::Relaxed);
38        if is_error {
39            self.total_errors.fetch_add(1, Ordering::Relaxed);
40        }
41        // Saturate to u64 microseconds to avoid overflow on pathological inputs.
42        let micros = latency.as_micros().min(u128::from(u64::MAX)) as u64;
43        self.total_latency_us.fetch_add(micros, Ordering::Relaxed);
44    }
45
46    /// Total number of recorded requests.
47    pub fn total_requests(&self) -> u64 {
48        self.total_requests.load(Ordering::Relaxed)
49    }
50
51    /// Mean response time in milliseconds over all recorded requests (0.0 if none).
52    pub fn avg_response_time_ms(&self) -> f64 {
53        let requests = self.total_requests.load(Ordering::Relaxed);
54        if requests == 0 {
55            return 0.0;
56        }
57        let total_us = self.total_latency_us.load(Ordering::Relaxed) as f64;
58        (total_us / requests as f64) / 1000.0
59    }
60
61    /// Error rate as a percentage over all recorded requests (0.0 if none).
62    pub fn error_rate_percent(&self) -> f64 {
63        let requests = self.total_requests.load(Ordering::Relaxed);
64        if requests == 0 {
65            return 0.0;
66        }
67        let errors = self.total_errors.load(Ordering::Relaxed) as f64;
68        (errors / requests as f64) * 100.0
69    }
70}
71
72/// API server state
73#[derive(Clone)]
74pub struct ApiState {
75    /// Model registry for managing deployed models
76    pub registry: Arc<ModelRegistry>,
77    /// Cache manager for performance optimization
78    pub cache_manager: Arc<CacheManager>,
79    /// Currently loaded models
80    pub models: Arc<RwLock<HashMap<Uuid, Arc<dyn EmbeddingModel + Send + Sync>>>>,
81    /// Live request metrics (latency/error tracking)
82    pub metrics: Arc<ApiMetrics>,
83    /// API configuration
84    pub config: ApiConfig,
85}
86
87/// API configuration
88#[derive(Debug, Clone)]
89pub struct ApiConfig {
90    /// Server host
91    pub host: String,
92    /// Server port
93    pub port: u16,
94    /// Request timeout in seconds
95    pub timeout_seconds: u64,
96    /// Request timeout in seconds (alias for axum)
97    pub request_timeout_secs: u64,
98    /// Maximum batch size for bulk operations
99    pub max_batch_size: usize,
100    /// Rate limiting configuration
101    pub rate_limit: RateLimitConfig,
102    /// Authentication configuration
103    pub auth: AuthConfig,
104    /// Enable request logging
105    pub enable_logging: bool,
106    /// Enable CORS
107    pub enable_cors: bool,
108}
109
110impl Default for ApiConfig {
111    fn default() -> Self {
112        Self {
113            host: "0.0.0.0".to_string(),
114            port: 8080,
115            timeout_seconds: 30,
116            request_timeout_secs: 30,
117            max_batch_size: 1000,
118            rate_limit: RateLimitConfig::default(),
119            auth: AuthConfig::default(),
120            enable_logging: true,
121            enable_cors: true,
122        }
123    }
124}
125
126/// Rate limiting configuration
127#[derive(Debug, Clone)]
128pub struct RateLimitConfig {
129    /// Requests per minute per IP
130    pub requests_per_minute: u32,
131    /// Enable rate limiting
132    pub enabled: bool,
133}
134
135impl Default for RateLimitConfig {
136    fn default() -> Self {
137        Self {
138            requests_per_minute: 1000,
139            enabled: true,
140        }
141    }
142}
143
144/// Authentication configuration
145#[derive(Debug, Clone, Default)]
146pub struct AuthConfig {
147    /// Enable API key authentication
148    pub require_api_key: bool,
149    /// Valid API keys
150    pub api_keys: Vec<String>,
151    /// Enable JWT authentication
152    pub enable_jwt: bool,
153    /// JWT secret
154    pub jwt_secret: Option<String>,
155}