omni_director/api_clean/
handlers.rs

1//! # API Handlers
2//!
3//! HTTP request handlers for the clean API layer.
4
5use super::responses::*;
6use crate::routing::{Router, Route};
7use crate::providers::{ProviderError, ProviderRegistry, EventRegistry};
8use axum::{
9    extract::{Path, Query, State},
10    http::StatusCode,
11    response::Json,
12};
13use serde::Deserialize;
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::time::{SystemTime, Instant};
17
18/// Application state shared across handlers
19#[derive(Clone)]
20pub struct AppState {
21    pub router: Arc<Router>,
22    pub registry: Arc<ProviderRegistry>,
23    pub event_registry: Arc<EventRegistry>,
24    pub start_time: SystemTime,
25}
26
27/// Query parameters for operation execution
28#[derive(Debug, Deserialize)]
29pub struct ExecuteQuery {
30    /// Additional query parameters passed as operation arguments
31    #[serde(flatten)]
32    pub args: HashMap<String, serde_json::Value>,
33}
34
35/// Request body for operation execution
36#[derive(Debug, Deserialize)]
37pub struct ExecuteRequest {
38    /// Operation arguments
39    #[serde(default)]
40    pub args: HashMap<String, serde_json::Value>,
41}
42
43/// Request body for unified exec_action endpoint
44#[derive(Debug, Deserialize)]
45pub struct ExecActionRequest {
46    /// Provider name
47    pub provider: String,
48    /// Feature name
49    pub feature: String,
50    /// Operation name
51    pub operation: String,
52    /// Operation arguments
53    #[serde(default)]
54    pub args: HashMap<String, serde_json::Value>,
55}
56
57/// Health check endpoint
58/// GET /health
59pub async fn health(State(state): State<AppState>) -> Json<ApiResponse<HealthStatus>> {
60    let uptime = state.start_time.elapsed()
61        .unwrap_or_default()
62        .as_secs();
63
64    let stats = state.registry.get_statistics().await;
65    
66    let health = HealthStatus {
67        status: "healthy".to_string(),
68        version: env!("CARGO_PKG_VERSION").to_string(),
69        uptime_seconds: uptime,
70        providers_loaded: stats.total_providers,
71        total_features: stats.total_features,
72        total_operations: stats.total_operations,
73        memory_usage_mb: get_memory_usage_mb(),
74    };
75
76    Json(ApiResponse::success(health))
77}
78
79/// Discovery endpoint - list all providers and their capabilities
80/// GET /providers
81pub async fn discover_providers(State(state): State<AppState>) -> Json<ApiResponse<DiscoveryResponse>> {
82    let metadata_list = state.registry.list_metadata().await;
83    
84    let providers: Vec<ProviderSummary> = metadata_list
85        .into_iter()
86        .map(|metadata| ProviderSummary {
87            name: metadata.name,
88            version: metadata.version,
89            description: metadata.description,
90            features: metadata.features
91                .into_iter()
92                .map(|feature| FeatureSummary {
93                    name: feature.name,
94                    description: feature.description,
95                    operations: feature.operations
96                        .into_iter()
97                        .map(|op| op.name)
98                        .collect(),
99                })
100                .collect(),
101        })
102        .collect();
103
104    let total_features = providers.iter().map(|p| p.features.len()).sum();
105    let total_operations = providers.iter()
106        .flat_map(|p| &p.features)
107        .map(|f| f.operations.len())
108        .sum();
109
110    let discovery = DiscoveryResponse {
111        total_providers: providers.len(),
112        total_features,
113        total_operations,
114        providers,
115    };
116
117    Json(ApiResponse::success(discovery))
118}
119
120/// Get specific provider information
121/// GET /providers/{provider}
122pub async fn get_provider(
123    Path(provider): Path<String>,
124    State(state): State<AppState>,
125) -> Result<Json<ApiResponse<ProviderInfo>>, StatusCode> {
126    match state.registry.get_metadata(&provider).await {
127        Some(metadata) => Ok(Json(ApiResponse::success(metadata.into()))),
128        None => Err(StatusCode::NOT_FOUND),
129    }
130}
131
132/// Get specific feature information
133/// GET /providers/{provider}/features/{feature}
134pub async fn get_feature(
135    Path((provider, feature)): Path<(String, String)>,
136    State(state): State<AppState>,
137) -> Result<Json<ApiResponse<FeatureInfo>>, StatusCode> {
138    let metadata = state.registry.get_metadata(&provider).await
139        .ok_or(StatusCode::NOT_FOUND)?;
140
141    let feature_metadata = metadata.features
142        .into_iter()
143        .find(|f| f.name == feature)
144        .ok_or(StatusCode::NOT_FOUND)?;
145
146    Ok(Json(ApiResponse::success(feature_metadata.into())))
147}
148
149/// Get specific operation information
150/// GET /providers/{provider}/features/{feature}/operations/{operation}
151pub async fn get_operation(
152    Path((provider, feature, operation)): Path<(String, String, String)>,
153    State(state): State<AppState>,
154) -> Result<Json<ApiResponse<OperationInfo>>, StatusCode> {
155    let route = Route::new(provider, feature, operation);
156    
157    match state.router.get_route_metadata(&route).await {
158        Ok(metadata) => {
159            let operation_info = OperationInfo {
160                name: metadata.operation_name,
161                description: metadata.operation_description,
162                arguments: metadata.operation_arguments.into_iter().map(Into::into).collect(),
163                return_type: metadata.operation_return_type,
164                is_mutating: metadata.is_mutating,
165                estimated_duration_ms: metadata.estimated_duration_ms,
166            };
167            Ok(Json(ApiResponse::success(operation_info)))
168        }
169        Err(_) => Err(StatusCode::NOT_FOUND),
170    }
171}
172
173/// Execute an operation
174/// POST /providers/{provider}/features/{feature}/operations/{operation}
175pub async fn execute_operation(
176    Path((provider, feature, operation)): Path<(String, String, String)>,
177    Query(query): Query<ExecuteQuery>,
178    State(state): State<AppState>,
179    body: Option<Json<ExecuteRequest>>,
180) -> Result<Json<ApiResponse<ExecutionResult>>, StatusCode> {
181    let start_time = Instant::now();
182    
183    // Combine query parameters and body arguments
184    let mut args = query.args;
185    if let Some(Json(request)) = body {
186        args.extend(request.args);
187    }
188
189    let route = Route::new(provider.clone(), feature.clone(), operation.clone());
190
191    match state.router.route_request(route, args).await {
192        Ok(result) => {
193            let execution_time = start_time.elapsed().as_millis() as u64;
194            
195            let execution_result = ExecutionResult {
196                result,
197                execution_time_ms: execution_time,
198                provider,
199                feature,
200                operation,
201            };
202
203            Ok(Json(ApiResponse::success(execution_result)))
204        }
205        Err(error) => {
206            let api_error = match &error {
207                ProviderError::NotFound(name) => {
208                    let msg = format!("Provider '{}' not found", name);
209                    ApiError::new("PROVIDER_NOT_FOUND", msg.as_str())
210                }
211                ProviderError::FeatureNotSupported { provider, feature } => {
212                    let msg = format!("Feature '{}' not supported by provider '{}'", feature, provider);
213                    ApiError::new("FEATURE_NOT_SUPPORTED", msg.as_str())
214                }
215                ProviderError::OperationNotSupported { provider, feature, operation } => {
216                    let msg = format!("Operation '{}' not supported by feature '{}' in provider '{}'", 
217                                     operation, feature, provider);
218                    ApiError::new("OPERATION_NOT_SUPPORTED", msg.as_str())
219                }
220                ProviderError::InvalidRoute(msg) => {
221                    ApiError::new("INVALID_ROUTE", msg.as_str())
222                }
223                ProviderError::ExecutionFailed(msg) => {
224                    ApiError::new("EXECUTION_FAILED", msg.as_str())
225                }
226                ProviderError::LoadingFailed(msg) => {
227                    ApiError::new("LOADING_FAILED", msg.as_str())
228                }
229                ProviderError::InitializationFailed(msg) => {
230                    ApiError::new("INITIALIZATION_FAILED", msg.as_str())
231                }
232                ProviderError::Io(err) => {
233                    ApiError::new("IO_ERROR", err.to_string().as_str())
234                }
235                ProviderError::LibraryError(err) => {
236                    ApiError::new("LIBRARY_ERROR", err.to_string().as_str())
237                }
238                ProviderError::InvalidArguments(msg) => {
239                    ApiError::new("INVALID_ARGUMENTS", msg.as_str())
240                }
241            };
242
243            let _status_code = match error {
244                ProviderError::NotFound(_) => StatusCode::NOT_FOUND,
245                ProviderError::FeatureNotSupported { .. } => StatusCode::NOT_FOUND,
246                ProviderError::OperationNotSupported { .. } => StatusCode::NOT_FOUND,
247                ProviderError::InvalidRoute(_) => StatusCode::BAD_REQUEST,
248                _ => StatusCode::INTERNAL_SERVER_ERROR,
249            };
250
251            // For error responses, we still return JSON but with error status code
252            let _error_response = ApiResponse::<ExecutionResult>::error(api_error);
253            
254            // This is a bit tricky - we want to return the error as JSON but with proper status code
255            // For now, let's return INTERNAL_SERVER_ERROR and let middleware handle it
256            Err(StatusCode::INTERNAL_SERVER_ERROR)
257        }
258    }
259}
260
261/// List all available routes
262/// GET /routes
263pub async fn list_routes(State(state): State<AppState>) -> Json<ApiResponse<Vec<String>>> {
264    match state.router.get_available_routes().await {
265        Ok(routes) => {
266            let route_paths: Vec<String> = routes
267                .into_iter()
268                .map(|route| route.to_url_path())
269                .collect();
270            Json(ApiResponse::success(route_paths))
271        }
272        Err(_) => {
273            let error = ApiError::new("ROUTES_UNAVAILABLE", "Failed to retrieve available routes");
274            Json(ApiResponse::error(error))
275        }
276    }
277}
278
279/// Registry statistics
280/// GET /stats
281pub async fn get_stats(State(state): State<AppState>) -> Json<ApiResponse<serde_json::Value>> {
282    let stats = state.registry.get_statistics().await;
283    // Convert to JSON to avoid trait issues
284    let stats_json = serde_json::to_value(stats).unwrap_or_default();
285    Json(ApiResponse::success(stats_json))
286}
287
288/// Unified action execution endpoint
289/// POST /exec_action
290pub async fn exec_action(
291    State(state): State<AppState>,
292    Json(request): Json<ExecActionRequest>,
293) -> Result<Json<ApiResponse<ExecutionResult>>, StatusCode> {
294    let start_time = Instant::now();
295    
296    // Create event name from feature and operation
297    let event_name = format!("{}.{}", request.feature, request.operation);
298    
299    // Convert args to serde_json::Value
300    let args_value = serde_json::to_value(request.args).unwrap_or_default();
301
302    match state.event_registry.execute(&event_name, args_value).await {
303        Ok(result) => {
304            let execution_time = start_time.elapsed().as_millis() as u64;
305            
306            let execution_result = ExecutionResult {
307                provider: request.provider,
308                feature: request.feature,
309                operation: request.operation,
310                result,
311                execution_time_ms: execution_time,
312            };
313            
314            Ok(Json(ApiResponse::success(execution_result)))
315        }
316        Err(e) => {
317            let error = if e.contains("not found") {
318                ApiError::new("EVENT_NOT_FOUND", &e)
319            } else {
320                ApiError::new("EXECUTION_FAILED", &e)
321            };
322            
323            Ok(Json(ApiResponse::error(error)))
324        }
325    }
326}
327
328/// Get memory usage (simplified version)
329fn get_memory_usage_mb() -> f64 {
330    // This is a simplified implementation
331    // In a real application, you might use a crate like `sysinfo` for accurate memory reporting
332    let _process = std::process::Command::new("tasklist")
333        .args(&["/FI", "PID eq {}", "/FO", "CSV"])
334        .output();
335    
336    // Return a placeholder value for now
337    0.0
338}