mockforge_core/workspace/
request.rs

1//! Mock request handling and processing
2//!
3//! This module provides functionality for processing mock requests,
4//! including request matching, response generation, and request execution.
5
6use crate::cache::{Cache, CachedResponse, ResponseCache};
7use crate::failure_analysis::FailureContextCollector;
8use crate::performance::PerformanceMonitor;
9use crate::templating::TemplateEngine;
10use crate::workspace::core::{EntityId, Folder, MockRequest, MockResponse, Workspace};
11use crate::{
12    routing::{HttpMethod, Route, RouteRegistry},
13    Error, Result,
14};
15use chrono::{DateTime, Utc};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::sync::Arc;
19use std::time::Duration;
20
21/// Request execution result
22#[derive(Debug, Clone)]
23pub struct RequestExecutionResult {
24    /// Request ID that was executed
25    pub request_id: EntityId,
26    /// Response that was returned
27    pub response: Option<MockResponse>,
28    /// Execution duration in milliseconds
29    pub duration_ms: u64,
30    /// Whether execution was successful
31    pub success: bool,
32    /// Error message if execution failed
33    pub error: Option<String>,
34    /// Failure context if execution failed (for root-cause analysis)
35    pub failure_context: Option<crate::failure_analysis::FailureContext>,
36}
37
38/// Request matching criteria
39#[derive(Debug, Clone)]
40pub struct RequestMatchCriteria {
41    /// HTTP method
42    pub method: HttpMethod,
43    /// Request path/URL
44    pub path: String,
45    /// Query parameters
46    pub query_params: HashMap<String, String>,
47    /// Headers
48    pub headers: HashMap<String, String>,
49    /// Body content (optional)
50    pub body: Option<String>,
51}
52
53/// Request processor for handling mock request execution
54#[derive(Debug, Clone)]
55pub struct RequestProcessor {
56    /// Template engine for variable substitution
57    _template_engine: TemplateEngine,
58    /// Environment manager for variable resolution
59    environment_manager: Option<crate::workspace::environment::EnvironmentManager>,
60    /// Performance monitoring
61    performance_monitor: Arc<PerformanceMonitor>,
62    /// Response cache for frequently accessed responses
63    response_cache: Arc<ResponseCache>,
64    /// Request validation cache
65    validation_cache: Arc<Cache<String, RequestValidationResult>>,
66    /// Enable performance optimizations
67    optimizations_enabled: bool,
68    /// Failure context collector for automatic failure analysis
69    failure_collector: Option<Arc<FailureContextCollector>>,
70}
71
72/// Request validation result
73#[derive(Debug, Clone)]
74pub struct RequestValidationResult {
75    /// Whether the request is valid
76    pub is_valid: bool,
77    /// Validation errors
78    pub errors: Vec<String>,
79    /// Validation warnings
80    pub warnings: Vec<String>,
81}
82
83/// Request execution context
84#[derive(Debug, Clone)]
85pub struct RequestExecutionContext {
86    /// Workspace ID
87    pub workspace_id: EntityId,
88    /// Environment variables
89    pub environment_variables: HashMap<String, String>,
90    /// Global headers
91    pub global_headers: HashMap<String, String>,
92    /// Request timeout in seconds
93    pub timeout_seconds: u64,
94    /// Whether SSL verification is enabled
95    pub ssl_verify: bool,
96}
97
98/// Request metrics
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct RequestMetrics {
101    /// Total requests executed
102    pub total_requests: u64,
103    /// Successful requests
104    pub successful_requests: u64,
105    /// Failed requests
106    pub failed_requests: u64,
107    /// Average response time in milliseconds
108    pub average_response_time_ms: f64,
109    /// Most popular requests
110    pub popular_requests: Vec<(EntityId, u64)>,
111    /// Last execution timestamp
112    pub last_execution: Option<DateTime<Utc>>,
113}
114
115impl RequestProcessor {
116    /// Create a new request processor
117    pub fn new() -> Self {
118        Self {
119            _template_engine: TemplateEngine::new(),
120            environment_manager: None,
121            performance_monitor: Arc::new(PerformanceMonitor::new()),
122            response_cache: Arc::new(ResponseCache::new(1000, Duration::from_secs(300))), // 5 min TTL
123            validation_cache: Arc::new(Cache::with_ttl(500, Duration::from_secs(60))), // 1 min TTL
124            optimizations_enabled: true,
125            failure_collector: Some(Arc::new(FailureContextCollector::new())),
126        }
127    }
128
129    /// Create a new request processor with environment manager
130    pub fn with_environment_manager(
131        environment_manager: crate::workspace::environment::EnvironmentManager,
132    ) -> Self {
133        Self {
134            _template_engine: TemplateEngine::new(),
135            environment_manager: Some(environment_manager),
136            performance_monitor: Arc::new(PerformanceMonitor::new()),
137            response_cache: Arc::new(ResponseCache::new(1000, Duration::from_secs(300))),
138            validation_cache: Arc::new(Cache::with_ttl(500, Duration::from_secs(60))),
139            optimizations_enabled: true,
140            failure_collector: Some(Arc::new(FailureContextCollector::new())),
141        }
142    }
143
144    /// Create a request processor with custom performance settings
145    pub fn with_performance_config(
146        environment_manager: Option<crate::workspace::environment::EnvironmentManager>,
147        cache_size: usize,
148        cache_ttl: Duration,
149        enable_optimizations: bool,
150    ) -> Self {
151        Self {
152            _template_engine: TemplateEngine::new(),
153            environment_manager,
154            performance_monitor: Arc::new(PerformanceMonitor::new()),
155            response_cache: Arc::new(ResponseCache::new(cache_size, cache_ttl)),
156            validation_cache: Arc::new(Cache::with_ttl(cache_size / 2, Duration::from_secs(60))),
157            optimizations_enabled: enable_optimizations,
158            failure_collector: Some(Arc::new(FailureContextCollector::new())),
159        }
160    }
161
162    /// Get performance monitor
163    pub fn performance_monitor(&self) -> Arc<PerformanceMonitor> {
164        self.performance_monitor.clone()
165    }
166
167    /// Enable or disable performance optimizations
168    pub fn set_optimizations_enabled(&mut self, enabled: bool) {
169        self.optimizations_enabled = enabled;
170    }
171
172    /// Find a request that matches the given criteria
173    pub fn find_matching_request(
174        &self,
175        workspace: &Workspace,
176        criteria: &RequestMatchCriteria,
177    ) -> Option<EntityId> {
178        // Search root requests
179        for request in &workspace.requests {
180            if self.request_matches(request, criteria) {
181                return Some(request.id.clone());
182            }
183        }
184
185        // Search folder requests
186        if let Some(request_id) =
187            self.find_matching_request_in_folders(&workspace.folders, criteria)
188        {
189            return Some(request_id);
190        }
191
192        None
193    }
194
195    /// Check if a request matches the given criteria
196    fn request_matches(&self, request: &MockRequest, criteria: &RequestMatchCriteria) -> bool {
197        // Check HTTP method
198        if request.method != criteria.method {
199            return false;
200        }
201
202        // Check URL pattern matching
203        if !self.url_matches_pattern(&request.url, &criteria.path) {
204            return false;
205        }
206
207        // Check query parameters
208        for (key, expected_value) in &criteria.query_params {
209            if let Some(actual_value) = request.query_params.get(key) {
210                if actual_value != expected_value {
211                    return false;
212                }
213            } else {
214                return false;
215            }
216        }
217
218        // Check headers (basic implementation)
219        for (key, expected_value) in &criteria.headers {
220            if let Some(actual_value) = request.headers.get(key) {
221                if actual_value != expected_value {
222                    return false;
223                }
224            } else {
225                return false;
226            }
227        }
228
229        true
230    }
231
232    /// Check if URL matches pattern
233    pub fn url_matches_pattern(&self, pattern: &str, url: &str) -> bool {
234        // Exact match
235        if pattern == url {
236            return true;
237        }
238
239        // Handle special case for root wildcard
240        if pattern == "*" {
241            return true;
242        }
243
244        // Handle wildcard patterns
245        if pattern.contains('*') {
246            return self.matches_path_pattern(pattern, url);
247        }
248
249        false
250    }
251
252    /// Check if a URL path matches a pattern with wildcards
253    fn matches_path_pattern(&self, pattern: &str, path: &str) -> bool {
254        let pattern_parts: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
255        let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
256
257        self.match_segments(&pattern_parts, &path_parts, 0, 0)
258    }
259
260    /// Recursive function to match path segments with wildcards
261    #[allow(clippy::only_used_in_recursion)]
262    fn match_segments(
263        &self,
264        pattern_parts: &[&str],
265        path_parts: &[&str],
266        pattern_idx: usize,
267        path_idx: usize,
268    ) -> bool {
269        // If we've consumed both patterns and paths, it's a match
270        if pattern_idx == pattern_parts.len() && path_idx == path_parts.len() {
271            return true;
272        }
273
274        // If we've consumed the pattern but not the path, no match
275        if pattern_idx == pattern_parts.len() {
276            return false;
277        }
278
279        let current_pattern = pattern_parts[pattern_idx];
280
281        match current_pattern {
282            "*" => {
283                // Single wildcard: try matching with current path segment
284                if path_idx < path_parts.len() {
285                    // Try consuming one segment
286                    if self.match_segments(pattern_parts, path_parts, pattern_idx + 1, path_idx + 1)
287                    {
288                        return true;
289                    }
290                }
291                false
292            }
293            "**" => {
294                // Double wildcard: can match zero or more segments
295                // Try matching zero segments (skip this pattern)
296                if self.match_segments(pattern_parts, path_parts, pattern_idx + 1, path_idx) {
297                    return true;
298                }
299                // Try matching one or more segments
300                if path_idx < path_parts.len()
301                    && self.match_segments(pattern_parts, path_parts, pattern_idx, path_idx + 1)
302                {
303                    return true;
304                }
305                false
306            }
307            _ => {
308                // Exact match required
309                if path_idx < path_parts.len() && current_pattern == path_parts[path_idx] {
310                    return self.match_segments(
311                        pattern_parts,
312                        path_parts,
313                        pattern_idx + 1,
314                        path_idx + 1,
315                    );
316                }
317                false
318            }
319        }
320    }
321
322    /// Find matching request in folder hierarchy
323    fn find_matching_request_in_folders(
324        &self,
325        folders: &[Folder],
326        criteria: &RequestMatchCriteria,
327    ) -> Option<EntityId> {
328        for folder in folders {
329            // Search folder requests
330            for request in &folder.requests {
331                if self.request_matches(request, criteria) {
332                    return Some(request.id.clone());
333                }
334            }
335
336            // Search subfolders
337            if let Some(request_id) =
338                self.find_matching_request_in_folders(&folder.folders, criteria)
339            {
340                return Some(request_id);
341            }
342        }
343
344        None
345    }
346
347    /// Execute a mock request
348    pub async fn execute_request(
349        &self,
350        workspace: &mut Workspace,
351        request_id: &EntityId,
352        context: &RequestExecutionContext,
353    ) -> Result<RequestExecutionResult> {
354        // Start performance tracking
355        let _perf_guard = if self.optimizations_enabled {
356            self.performance_monitor.start_tracking_named("execute_request")
357        } else {
358            None
359        };
360
361        // Generate cache key for response caching if optimizations are enabled
362        let cache_key = if self.optimizations_enabled {
363            self.generate_response_cache_key(request_id, context)
364        } else {
365            String::new()
366        };
367
368        // Check response cache first
369        if self.optimizations_enabled && !cache_key.is_empty() {
370            if let Some(cached_response) = self.response_cache.get_response(&cache_key).await {
371                self.performance_monitor.record_cache_hit();
372                return Ok(RequestExecutionResult {
373                    request_id: request_id.clone(),
374                    response: Some(self.convert_cached_response_to_mock_response(cached_response)),
375                    duration_ms: 1, // Cached responses are nearly instant
376                    success: true,
377                    error: None,
378                    failure_context: None,
379                });
380            } else {
381                self.performance_monitor.record_cache_miss();
382            }
383        }
384
385        // Find the request
386        let request = match self.find_request_in_workspace(workspace, request_id) {
387            Some(req) => req,
388            None => {
389                if self.optimizations_enabled {
390                    self.performance_monitor.record_error();
391                }
392                let error_msg = format!("Request with ID {} not found", request_id);
393
394                // Capture failure context if collector is available
395                // Note: The failure_context is available in RequestExecutionResult for callers to store
396                // if needed. The caller (e.g., API handler) can persist it to a failure store.
397                if let Some(ref collector) = self.failure_collector {
398                    let _failure_context = collector
399                        .collect_context(
400                            "UNKNOWN",
401                            &request_id.to_string(),
402                            None,
403                            Some(error_msg.clone()),
404                        )
405                        .ok();
406                }
407
408                return Err(Error::generic(error_msg));
409            }
410        };
411
412        let start_time = std::time::Instant::now();
413        let method = "GET"; // Default, could be extracted from request if available
414        let path = request_id.to_string(); // Use request ID as path identifier
415
416        // Validate request with caching
417        let validation = match self.validate_request_cached(request, context).await {
418            Ok(v) => v,
419            Err(e) => {
420                if self.optimizations_enabled {
421                    self.performance_monitor.record_error();
422                }
423                let error_msg = format!("Request validation error: {}", e);
424
425                // Capture failure context
426                // Note: The failure_context is available in RequestExecutionResult for callers to store
427                // if needed. The caller (e.g., API handler) can persist it to a failure store.
428                if let Some(ref collector) = self.failure_collector {
429                    let _failure_context = collector
430                        .collect_context(method, &path, None, Some(error_msg.clone()))
431                        .ok();
432                }
433
434                return Err(e);
435            }
436        };
437
438        if !validation.is_valid {
439            if self.optimizations_enabled {
440                self.performance_monitor.record_error();
441            }
442            let error_msg = format!("Request validation failed: {:?}", validation.errors);
443
444            // Capture failure context
445            // Note: The failure_context is available in RequestExecutionResult for callers to store
446            // if needed. The caller (e.g., API handler) can persist it to a failure store.
447            if let Some(ref collector) = self.failure_collector {
448                let _failure_context =
449                    collector.collect_context(method, &path, None, Some(error_msg.clone())).ok();
450            }
451
452            return Err(Error::Validation { message: error_msg });
453        }
454
455        // Get active response
456        let response = match request.active_response() {
457            Some(resp) => resp,
458            None => {
459                if self.optimizations_enabled {
460                    self.performance_monitor.record_error();
461                }
462                let error_msg = "No active response found for request".to_string();
463
464                // Capture failure context
465                // Note: The failure_context is available in RequestExecutionResult for callers to store
466                // if needed. The caller (e.g., API handler) can persist it to a failure store.
467                if let Some(ref collector) = self.failure_collector {
468                    let _failure_context = collector
469                        .collect_context(method, &path, None, Some(error_msg.clone()))
470                        .ok();
471                }
472
473                return Err(Error::generic(error_msg));
474            }
475        };
476
477        // Apply variable substitution
478        let processed_response = match self.process_response(response, context).await {
479            Ok(resp) => resp,
480            Err(e) => {
481                if self.optimizations_enabled {
482                    self.performance_monitor.record_error();
483                }
484                let error_msg = format!("Failed to process response: {}", e);
485
486                // Capture failure context
487                // Note: The failure_context is available in RequestExecutionResult for callers to store
488                // if needed. The caller (e.g., API handler) can persist it to a failure store.
489                if let Some(ref collector) = self.failure_collector {
490                    let _failure_context = collector
491                        .collect_context(method, &path, None, Some(error_msg.clone()))
492                        .ok();
493                }
494
495                return Err(e);
496            }
497        };
498
499        let duration_ms = start_time.elapsed().as_millis() as u64;
500
501        // Cache the response if optimizations are enabled
502        if self.optimizations_enabled && !cache_key.is_empty() {
503            let cached_response =
504                self.convert_mock_response_to_cached_response(&processed_response);
505            self.response_cache.cache_response(cache_key, cached_response).await;
506        }
507
508        // Record response usage
509        if let Some(request_mut) = self.find_request_in_workspace_mut(workspace, request_id) {
510            if let Some(response_mut) = request_mut.active_response_mut() {
511                response_mut.record_usage(request_id.clone(), duration_ms);
512            }
513        }
514
515        Ok(RequestExecutionResult {
516            request_id: request_id.clone(),
517            response: Some(processed_response),
518            duration_ms,
519            success: true,
520            error: None,
521            failure_context: None,
522        })
523    }
524
525    /// Find request in workspace (mutable)
526    fn find_request_in_workspace_mut<'a>(
527        &self,
528        workspace: &'a mut Workspace,
529        request_id: &EntityId,
530    ) -> Option<&'a mut MockRequest> {
531        // Search root requests
532        for request in &mut workspace.requests {
533            if &request.id == request_id {
534                return Some(request);
535            }
536        }
537
538        // Search folder requests
539        self.find_request_in_folders_mut(&mut workspace.folders, request_id)
540    }
541
542    /// Find request in folder hierarchy (mutable)
543    #[allow(clippy::only_used_in_recursion)]
544    fn find_request_in_folders_mut<'a>(
545        &self,
546        folders: &'a mut [Folder],
547        request_id: &EntityId,
548    ) -> Option<&'a mut MockRequest> {
549        for folder in folders {
550            // Search folder requests
551            for request in &mut folder.requests {
552                if &request.id == request_id {
553                    return Some(request);
554                }
555            }
556
557            // Search subfolders
558            if let Some(request) = self.find_request_in_folders_mut(&mut folder.folders, request_id)
559            {
560                return Some(request);
561            }
562        }
563
564        None
565    }
566
567    /// Find request in workspace (immutable)
568    fn find_request_in_workspace<'a>(
569        &self,
570        workspace: &'a Workspace,
571        request_id: &EntityId,
572    ) -> Option<&'a MockRequest> {
573        // Search root requests
574        workspace
575            .requests
576            .iter()
577            .find(|r| &r.id == request_id)
578            .or_else(|| self.find_request_in_folders(&workspace.folders, request_id))
579    }
580
581    /// Find request in folder hierarchy (immutable)
582    #[allow(clippy::only_used_in_recursion)]
583    fn find_request_in_folders<'a>(
584        &self,
585        folders: &'a [Folder],
586        request_id: &EntityId,
587    ) -> Option<&'a MockRequest> {
588        for folder in folders {
589            // Search folder requests
590            if let Some(request) = folder.requests.iter().find(|r| &r.id == request_id) {
591                return Some(request);
592            }
593
594            // Search subfolders
595            if let Some(request) = self.find_request_in_folders(&folder.folders, request_id) {
596                return Some(request);
597            }
598        }
599
600        None
601    }
602
603    /// Validate a request
604    pub fn validate_request(
605        &self,
606        request: &MockRequest,
607        _context: &RequestExecutionContext,
608    ) -> RequestValidationResult {
609        let mut errors = Vec::new();
610        let mut warnings = Vec::new();
611
612        // Check if request is enabled
613        if !request.enabled {
614            errors.push("Request is disabled".to_string());
615        }
616
617        // Validate URL
618        if request.url.is_empty() {
619            errors.push("Request URL cannot be empty".to_string());
620        }
621
622        // Validate method
623        match request.method {
624            HttpMethod::GET
625            | HttpMethod::POST
626            | HttpMethod::PUT
627            | HttpMethod::DELETE
628            | HttpMethod::PATCH
629            | HttpMethod::HEAD
630            | HttpMethod::OPTIONS => {
631                // Valid methods
632            }
633        }
634
635        // Check for active response
636        if request.active_response().is_none() {
637            warnings.push("No active response configured".to_string());
638        }
639
640        // Validate responses
641        for response in &request.responses {
642            if response.status_code < 100 || response.status_code > 599 {
643                errors.push(format!("Invalid status code: {}", response.status_code));
644            }
645
646            if response.body.is_empty() {
647                warnings.push(format!("Response '{}' has empty body", response.name));
648            }
649        }
650
651        RequestValidationResult {
652            is_valid: errors.is_empty(),
653            errors,
654            warnings,
655        }
656    }
657
658    /// Process response with variable substitution and delays
659    async fn process_response(
660        &self,
661        response: &MockResponse,
662        context: &RequestExecutionContext,
663    ) -> Result<MockResponse> {
664        // Apply delay if configured
665        if response.delay > 0 {
666            tokio::time::sleep(std::time::Duration::from_millis(response.delay)).await;
667        }
668
669        // Create processed response
670        let mut processed_response = response.clone();
671
672        // Apply environment variable substitution
673        if let Some(env_manager) = &self.environment_manager {
674            if let Some(_env_vars) = self.get_environment_variables(context) {
675                processed_response.body = env_manager.substitute_variables(&response.body).value;
676            }
677        }
678
679        Ok(processed_response)
680    }
681
682    /// Get environment variables for context
683    fn get_environment_variables(
684        &self,
685        context: &RequestExecutionContext,
686    ) -> Option<HashMap<String, String>> {
687        if let Some(env_manager) = &self.environment_manager {
688            if let Some(active_env) = env_manager.get_active_environment() {
689                return Some(active_env.variables.clone());
690            }
691        }
692
693        Some(context.environment_variables.clone())
694    }
695
696    /// Get request metrics for a workspace
697    pub fn get_request_metrics(&self, workspace: &Workspace) -> RequestMetrics {
698        let mut total_requests = 0u64;
699        let mut successful_requests = 0u64;
700        let mut failed_requests = 0u64;
701        let mut total_response_time = 0u64;
702        let mut request_counts = HashMap::new();
703        let mut last_execution: Option<DateTime<Utc>> = None;
704
705        // Collect metrics from all requests
706        for request in &workspace.requests {
707            total_requests += 1;
708
709            // Count executions from response history
710            for response in &request.responses {
711                let execution_count = response.history.len() as u64;
712                *request_counts.entry(request.id.clone()).or_insert(0) += execution_count;
713
714                for entry in &response.history {
715                    total_response_time += entry.duration_ms;
716
717                    // Update last execution timestamp
718                    if let Some(current_last) = last_execution {
719                        if entry.timestamp > current_last {
720                            last_execution = Some(entry.timestamp);
721                        }
722                    } else {
723                        last_execution = Some(entry.timestamp);
724                    }
725
726                    // Simple success determination (could be improved)
727                    if entry.duration_ms < 5000 {
728                        // Less than 5 seconds
729                        successful_requests += 1;
730                    } else {
731                        failed_requests += 1;
732                    }
733                }
734            }
735        }
736
737        // Also collect from folder requests
738        self.collect_folder_request_metrics(
739            &workspace.folders,
740            &mut total_requests,
741            &mut successful_requests,
742            &mut failed_requests,
743            &mut total_response_time,
744            &mut request_counts,
745            &mut last_execution,
746        );
747
748        let average_response_time = if total_requests > 0 {
749            total_response_time as f64 / total_requests as f64
750        } else {
751            0.0
752        };
753
754        // Get popular requests (top 5)
755        let mut popular_requests: Vec<_> = request_counts.into_iter().collect();
756        popular_requests.sort_by(|a, b| b.1.cmp(&a.1));
757        popular_requests.truncate(5);
758
759        RequestMetrics {
760            total_requests,
761            successful_requests,
762            failed_requests,
763            average_response_time_ms: average_response_time,
764            popular_requests,
765            last_execution,
766        }
767    }
768
769    /// Collect metrics from folder requests
770    #[allow(clippy::only_used_in_recursion)]
771    #[allow(clippy::too_many_arguments)]
772    fn collect_folder_request_metrics(
773        &self,
774        folders: &[Folder],
775        total_requests: &mut u64,
776        successful_requests: &mut u64,
777        failed_requests: &mut u64,
778        total_response_time: &mut u64,
779        request_counts: &mut HashMap<EntityId, u64>,
780        last_execution: &mut Option<DateTime<Utc>>,
781    ) {
782        for folder in folders {
783            for request in &folder.requests {
784                *total_requests += 1;
785
786                // Count executions from response history
787                for response in &request.responses {
788                    let execution_count = response.history.len() as u64;
789                    *request_counts.entry(request.id.clone()).or_insert(0) += execution_count;
790
791                    for entry in &response.history {
792                        *total_response_time += entry.duration_ms;
793
794                        // Update last execution timestamp
795                        if let Some(current_last) = *last_execution {
796                            if entry.timestamp > current_last {
797                                *last_execution = Some(entry.timestamp);
798                            }
799                        } else {
800                            *last_execution = Some(entry.timestamp);
801                        }
802
803                        // Simple success determination
804                        if entry.duration_ms < 5000 {
805                            *successful_requests += 1;
806                        } else {
807                            *failed_requests += 1;
808                        }
809                    }
810                }
811            }
812
813            // Recurse into subfolders
814            self.collect_folder_request_metrics(
815                &folder.folders,
816                total_requests,
817                successful_requests,
818                failed_requests,
819                total_response_time,
820                request_counts,
821                last_execution,
822            );
823        }
824    }
825
826    /// Create a route from a mock request
827    pub fn create_route_from_request(&self, request: &MockRequest) -> Result<Route> {
828        if !request.enabled {
829            return Err(Error::validation("Request is disabled"));
830        }
831
832        let response = request
833            .active_response()
834            .ok_or_else(|| Error::validation("No active response found"))?;
835
836        // Create route with request information
837        let mut route = Route::new(request.method.clone(), request.url.clone());
838
839        // Store additional data in metadata
840        route.metadata.insert("id".to_string(), serde_json::json!(request.id));
841        route.metadata.insert("response".to_string(), serde_json::json!(response.body));
842        route
843            .metadata
844            .insert("status_code".to_string(), serde_json::json!(response.status_code));
845        route.metadata.insert("headers".to_string(), serde_json::json!(request.headers));
846        route
847            .metadata
848            .insert("query_params".to_string(), serde_json::json!(request.query_params));
849        route.metadata.insert("enabled".to_string(), serde_json::json!(request.enabled));
850        route
851            .metadata
852            .insert("created_at".to_string(), serde_json::json!(request.created_at));
853        route
854            .metadata
855            .insert("updated_at".to_string(), serde_json::json!(request.updated_at));
856
857        Ok(route)
858    }
859
860    /// Update route registry with workspace requests
861    pub fn update_route_registry(
862        &self,
863        workspace: &Workspace,
864        route_registry: &mut RouteRegistry,
865    ) -> Result<()> {
866        route_registry.clear();
867
868        // Add root requests
869        for request in &workspace.requests {
870            if request.enabled {
871                if let Ok(route) = self.create_route_from_request(request) {
872                    let _ = route_registry.add_route(route);
873                }
874            }
875        }
876
877        // Add folder requests
878        self.add_folder_routes_to_registry(&workspace.folders, route_registry)?;
879
880        Ok(())
881    }
882
883    /// Add folder requests to route registry
884    fn add_folder_routes_to_registry(
885        &self,
886        folders: &[Folder],
887        route_registry: &mut RouteRegistry,
888    ) -> Result<()> {
889        for folder in folders {
890            for request in &folder.requests {
891                if request.enabled {
892                    if let Ok(route) = self.create_route_from_request(request) {
893                        let _ = route_registry.add_route(route);
894                    }
895                }
896            }
897
898            // Recurse into subfolders
899            self.add_folder_routes_to_registry(&folder.folders, route_registry)?;
900        }
901
902        Ok(())
903    }
904
905    // Performance optimization helper methods
906
907    /// Generate cache key for response caching
908    fn generate_response_cache_key(
909        &self,
910        request_id: &EntityId,
911        context: &RequestExecutionContext,
912    ) -> String {
913        use std::collections::hash_map::DefaultHasher;
914        use std::hash::{Hash, Hasher};
915
916        let mut hasher = DefaultHasher::new();
917        request_id.hash(&mut hasher);
918        context.workspace_id.hash(&mut hasher);
919
920        // Hash environment variables
921        for (key, value) in &context.environment_variables {
922            key.hash(&mut hasher);
923            value.hash(&mut hasher);
924        }
925
926        // Hash global headers
927        for (key, value) in &context.global_headers {
928            key.hash(&mut hasher);
929            value.hash(&mut hasher);
930        }
931
932        format!("req_{}_{}", hasher.finish(), request_id)
933    }
934
935    /// Validate request with caching
936    async fn validate_request_cached(
937        &self,
938        request: &MockRequest,
939        context: &RequestExecutionContext,
940    ) -> Result<RequestValidationResult> {
941        if !self.optimizations_enabled {
942            return Ok(self.validate_request(request, context));
943        }
944
945        // Generate validation cache key
946        let cache_key = format!("val_{}_{}", request.id, context.workspace_id);
947
948        // Check cache first
949        if let Some(cached_result) = self.validation_cache.get(&cache_key).await {
950            return Ok(cached_result);
951        }
952
953        // Perform validation
954        let result = self.validate_request(request, context);
955
956        // Cache the result
957        self.validation_cache.insert(cache_key, result.clone(), None).await;
958
959        Ok(result)
960    }
961
962    /// Convert MockResponse to CachedResponse
963    fn convert_mock_response_to_cached_response(&self, response: &MockResponse) -> CachedResponse {
964        CachedResponse {
965            status_code: response.status_code,
966            headers: response.headers.clone(),
967            body: response.body.clone(),
968            content_type: response.headers.get("Content-Type").cloned(),
969        }
970    }
971
972    /// Convert CachedResponse to MockResponse
973    fn convert_cached_response_to_mock_response(&self, cached: CachedResponse) -> MockResponse {
974        MockResponse {
975            id: EntityId::new(),
976            name: "Cached Response".to_string(),
977            status_code: cached.status_code,
978            headers: cached.headers,
979            body: cached.body,
980            delay: 0, // Cached responses have no additional delay
981            active: true,
982            created_at: chrono::Utc::now(),
983            updated_at: chrono::Utc::now(),
984            history: Vec::new(),
985            intelligent: None,
986            drift: None,
987        }
988    }
989
990    /// Get performance summary
991    pub async fn get_performance_summary(&self) -> crate::performance::PerformanceSummary {
992        self.performance_monitor.get_summary().await
993    }
994
995    /// Get cache statistics
996    pub async fn get_cache_stats(&self) -> (crate::cache::CacheStats, crate::cache::CacheStats) {
997        let response_cache_stats = self.response_cache.stats().await;
998        let validation_cache_stats = self.validation_cache.stats().await;
999        (response_cache_stats, validation_cache_stats)
1000    }
1001
1002    /// Clear all caches
1003    pub async fn clear_caches(&self) {
1004        self.response_cache.get_response("").await; // Dummy call to access underlying cache
1005        self.validation_cache.clear().await;
1006    }
1007}
1008
1009impl Default for RequestProcessor {
1010    fn default() -> Self {
1011        Self::new()
1012    }
1013}