1use crate::cache::{Cache, CachedResponse, ResponseCache};
7use crate::performance::PerformanceMonitor;
8use crate::templating::TemplateEngine;
9use crate::workspace::core::{EntityId, Folder, MockRequest, MockResponse, Workspace};
10use crate::{
11 routing::{HttpMethod, Route, RouteRegistry},
12 Error, Result,
13};
14use chrono::{DateTime, Utc};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use std::time::Duration;
19
20#[derive(Debug, Clone)]
22pub struct RequestExecutionResult {
23 pub request_id: EntityId,
25 pub response: Option<MockResponse>,
27 pub duration_ms: u64,
29 pub success: bool,
31 pub error: Option<String>,
33}
34
35#[derive(Debug, Clone)]
37pub struct RequestMatchCriteria {
38 pub method: HttpMethod,
40 pub path: String,
42 pub query_params: HashMap<String, String>,
44 pub headers: HashMap<String, String>,
46 pub body: Option<String>,
48}
49
50#[derive(Debug, Clone)]
52pub struct RequestProcessor {
53 _template_engine: TemplateEngine,
55 environment_manager: Option<crate::workspace::environment::EnvironmentManager>,
57 performance_monitor: Arc<PerformanceMonitor>,
59 response_cache: Arc<ResponseCache>,
61 validation_cache: Arc<Cache<String, RequestValidationResult>>,
63 optimizations_enabled: bool,
65}
66
67#[derive(Debug, Clone)]
69pub struct RequestValidationResult {
70 pub is_valid: bool,
72 pub errors: Vec<String>,
74 pub warnings: Vec<String>,
76}
77
78#[derive(Debug, Clone)]
80pub struct RequestExecutionContext {
81 pub workspace_id: EntityId,
83 pub environment_variables: HashMap<String, String>,
85 pub global_headers: HashMap<String, String>,
87 pub timeout_seconds: u64,
89 pub ssl_verify: bool,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct RequestMetrics {
96 pub total_requests: u64,
98 pub successful_requests: u64,
100 pub failed_requests: u64,
102 pub average_response_time_ms: f64,
104 pub popular_requests: Vec<(EntityId, u64)>,
106 pub last_execution: Option<DateTime<Utc>>,
108}
109
110impl RequestProcessor {
111 pub fn new() -> Self {
113 Self {
114 _template_engine: TemplateEngine::new(),
115 environment_manager: None,
116 performance_monitor: Arc::new(PerformanceMonitor::new()),
117 response_cache: Arc::new(ResponseCache::new(1000, Duration::from_secs(300))), validation_cache: Arc::new(Cache::with_ttl(500, Duration::from_secs(60))), optimizations_enabled: true,
120 }
121 }
122
123 pub fn with_environment_manager(
125 environment_manager: crate::workspace::environment::EnvironmentManager,
126 ) -> Self {
127 Self {
128 _template_engine: TemplateEngine::new(),
129 environment_manager: Some(environment_manager),
130 performance_monitor: Arc::new(PerformanceMonitor::new()),
131 response_cache: Arc::new(ResponseCache::new(1000, Duration::from_secs(300))),
132 validation_cache: Arc::new(Cache::with_ttl(500, Duration::from_secs(60))),
133 optimizations_enabled: true,
134 }
135 }
136
137 pub fn with_performance_config(
139 environment_manager: Option<crate::workspace::environment::EnvironmentManager>,
140 cache_size: usize,
141 cache_ttl: Duration,
142 enable_optimizations: bool,
143 ) -> Self {
144 Self {
145 _template_engine: TemplateEngine::new(),
146 environment_manager,
147 performance_monitor: Arc::new(PerformanceMonitor::new()),
148 response_cache: Arc::new(ResponseCache::new(cache_size, cache_ttl)),
149 validation_cache: Arc::new(Cache::with_ttl(cache_size / 2, Duration::from_secs(60))),
150 optimizations_enabled: enable_optimizations,
151 }
152 }
153
154 pub fn performance_monitor(&self) -> Arc<PerformanceMonitor> {
156 self.performance_monitor.clone()
157 }
158
159 pub fn set_optimizations_enabled(&mut self, enabled: bool) {
161 self.optimizations_enabled = enabled;
162 }
163
164 pub fn find_matching_request(
166 &self,
167 workspace: &Workspace,
168 criteria: &RequestMatchCriteria,
169 ) -> Option<EntityId> {
170 for request in &workspace.requests {
172 if self.request_matches(request, criteria) {
173 return Some(request.id.clone());
174 }
175 }
176
177 if let Some(request_id) =
179 self.find_matching_request_in_folders(&workspace.folders, criteria)
180 {
181 return Some(request_id);
182 }
183
184 None
185 }
186
187 fn request_matches(&self, request: &MockRequest, criteria: &RequestMatchCriteria) -> bool {
189 if request.method != criteria.method {
191 return false;
192 }
193
194 if !self.url_matches_pattern(&request.url, &criteria.path) {
196 return false;
197 }
198
199 for (key, expected_value) in &criteria.query_params {
201 if let Some(actual_value) = request.query_params.get(key) {
202 if actual_value != expected_value {
203 return false;
204 }
205 } else {
206 return false;
207 }
208 }
209
210 for (key, expected_value) in &criteria.headers {
212 if let Some(actual_value) = request.headers.get(key) {
213 if actual_value != expected_value {
214 return false;
215 }
216 } else {
217 return false;
218 }
219 }
220
221 true
222 }
223
224 pub fn url_matches_pattern(&self, pattern: &str, url: &str) -> bool {
226 if pattern == url {
228 return true;
229 }
230
231 if pattern == "*" {
233 return true;
234 }
235
236 if pattern.contains('*') {
238 return self.matches_path_pattern(pattern, url);
239 }
240
241 false
242 }
243
244 fn matches_path_pattern(&self, pattern: &str, path: &str) -> bool {
246 let pattern_parts: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
247 let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
248
249 self.match_segments(&pattern_parts, &path_parts, 0, 0)
250 }
251
252 #[allow(clippy::only_used_in_recursion)]
254 fn match_segments(
255 &self,
256 pattern_parts: &[&str],
257 path_parts: &[&str],
258 pattern_idx: usize,
259 path_idx: usize,
260 ) -> bool {
261 if pattern_idx == pattern_parts.len() && path_idx == path_parts.len() {
263 return true;
264 }
265
266 if pattern_idx == pattern_parts.len() {
268 return false;
269 }
270
271 let current_pattern = pattern_parts[pattern_idx];
272
273 match current_pattern {
274 "*" => {
275 if path_idx < path_parts.len() {
277 if self.match_segments(pattern_parts, path_parts, pattern_idx + 1, path_idx + 1)
279 {
280 return true;
281 }
282 }
283 false
284 }
285 "**" => {
286 if self.match_segments(pattern_parts, path_parts, pattern_idx + 1, path_idx) {
289 return true;
290 }
291 if path_idx < path_parts.len()
293 && self.match_segments(pattern_parts, path_parts, pattern_idx, path_idx + 1)
294 {
295 return true;
296 }
297 false
298 }
299 _ => {
300 if path_idx < path_parts.len() && current_pattern == path_parts[path_idx] {
302 return self.match_segments(
303 pattern_parts,
304 path_parts,
305 pattern_idx + 1,
306 path_idx + 1,
307 );
308 }
309 false
310 }
311 }
312 }
313
314 fn find_matching_request_in_folders(
316 &self,
317 folders: &[Folder],
318 criteria: &RequestMatchCriteria,
319 ) -> Option<EntityId> {
320 for folder in folders {
321 for request in &folder.requests {
323 if self.request_matches(request, criteria) {
324 return Some(request.id.clone());
325 }
326 }
327
328 if let Some(request_id) =
330 self.find_matching_request_in_folders(&folder.folders, criteria)
331 {
332 return Some(request_id);
333 }
334 }
335
336 None
337 }
338
339 pub async fn execute_request(
341 &self,
342 workspace: &mut Workspace,
343 request_id: &EntityId,
344 context: &RequestExecutionContext,
345 ) -> Result<RequestExecutionResult> {
346 let _perf_guard = if self.optimizations_enabled {
348 self.performance_monitor.start_tracking_named("execute_request")
349 } else {
350 None
351 };
352
353 let cache_key = if self.optimizations_enabled {
355 self.generate_response_cache_key(request_id, context)
356 } else {
357 String::new()
358 };
359
360 if self.optimizations_enabled && !cache_key.is_empty() {
362 if let Some(cached_response) = self.response_cache.get_response(&cache_key).await {
363 self.performance_monitor.record_cache_hit();
364 return Ok(RequestExecutionResult {
365 request_id: request_id.clone(),
366 response: Some(self.convert_cached_response_to_mock_response(cached_response)),
367 duration_ms: 1, success: true,
369 error: None,
370 });
371 } else {
372 self.performance_monitor.record_cache_miss();
373 }
374 }
375
376 let request = self.find_request_in_workspace(workspace, request_id).ok_or_else(|| {
378 if self.optimizations_enabled {
379 self.performance_monitor.record_error();
380 }
381 format!("Request with ID {} not found", request_id)
382 })?;
383
384 let start_time = std::time::Instant::now();
385
386 let validation = self.validate_request_cached(request, context).await?;
388 if !validation.is_valid {
389 if self.optimizations_enabled {
390 self.performance_monitor.record_error();
391 }
392 return Err(Error::Validation {
393 message: format!("Request validation failed: {:?}", validation.errors),
394 });
395 }
396
397 let response = request.active_response().ok_or_else(|| {
399 if self.optimizations_enabled {
400 self.performance_monitor.record_error();
401 }
402 Error::generic("No active response found for request")
403 })?;
404
405 let processed_response = self.process_response(response, context).await?;
407
408 let duration_ms = start_time.elapsed().as_millis() as u64;
409
410 if self.optimizations_enabled && !cache_key.is_empty() {
412 let cached_response =
413 self.convert_mock_response_to_cached_response(&processed_response);
414 self.response_cache.cache_response(cache_key, cached_response).await;
415 }
416
417 if let Some(request_mut) = self.find_request_in_workspace_mut(workspace, request_id) {
419 if let Some(response_mut) = request_mut.active_response_mut() {
420 response_mut.record_usage(request_id.clone(), duration_ms);
421 }
422 }
423
424 Ok(RequestExecutionResult {
425 request_id: request_id.clone(),
426 response: Some(processed_response),
427 duration_ms,
428 success: true,
429 error: None,
430 })
431 }
432
433 fn find_request_in_workspace_mut<'a>(
435 &self,
436 workspace: &'a mut Workspace,
437 request_id: &EntityId,
438 ) -> Option<&'a mut MockRequest> {
439 for request in &mut workspace.requests {
441 if &request.id == request_id {
442 return Some(request);
443 }
444 }
445
446 self.find_request_in_folders_mut(&mut workspace.folders, request_id)
448 }
449
450 #[allow(clippy::only_used_in_recursion)]
452 fn find_request_in_folders_mut<'a>(
453 &self,
454 folders: &'a mut [Folder],
455 request_id: &EntityId,
456 ) -> Option<&'a mut MockRequest> {
457 for folder in folders {
458 for request in &mut folder.requests {
460 if &request.id == request_id {
461 return Some(request);
462 }
463 }
464
465 if let Some(request) = self.find_request_in_folders_mut(&mut folder.folders, request_id)
467 {
468 return Some(request);
469 }
470 }
471
472 None
473 }
474
475 fn find_request_in_workspace<'a>(
477 &self,
478 workspace: &'a Workspace,
479 request_id: &EntityId,
480 ) -> Option<&'a MockRequest> {
481 workspace
483 .requests
484 .iter()
485 .find(|r| &r.id == request_id)
486 .or_else(|| self.find_request_in_folders(&workspace.folders, request_id))
487 }
488
489 #[allow(clippy::only_used_in_recursion)]
491 fn find_request_in_folders<'a>(
492 &self,
493 folders: &'a [Folder],
494 request_id: &EntityId,
495 ) -> Option<&'a MockRequest> {
496 for folder in folders {
497 if let Some(request) = folder.requests.iter().find(|r| &r.id == request_id) {
499 return Some(request);
500 }
501
502 if let Some(request) = self.find_request_in_folders(&folder.folders, request_id) {
504 return Some(request);
505 }
506 }
507
508 None
509 }
510
511 pub fn validate_request(
513 &self,
514 request: &MockRequest,
515 _context: &RequestExecutionContext,
516 ) -> RequestValidationResult {
517 let mut errors = Vec::new();
518 let mut warnings = Vec::new();
519
520 if !request.enabled {
522 errors.push("Request is disabled".to_string());
523 }
524
525 if request.url.is_empty() {
527 errors.push("Request URL cannot be empty".to_string());
528 }
529
530 match request.method {
532 HttpMethod::GET
533 | HttpMethod::POST
534 | HttpMethod::PUT
535 | HttpMethod::DELETE
536 | HttpMethod::PATCH
537 | HttpMethod::HEAD
538 | HttpMethod::OPTIONS => {
539 }
541 }
542
543 if request.active_response().is_none() {
545 warnings.push("No active response configured".to_string());
546 }
547
548 for response in &request.responses {
550 if response.status_code < 100 || response.status_code > 599 {
551 errors.push(format!("Invalid status code: {}", response.status_code));
552 }
553
554 if response.body.is_empty() {
555 warnings.push(format!("Response '{}' has empty body", response.name));
556 }
557 }
558
559 RequestValidationResult {
560 is_valid: errors.is_empty(),
561 errors,
562 warnings,
563 }
564 }
565
566 async fn process_response(
568 &self,
569 response: &MockResponse,
570 context: &RequestExecutionContext,
571 ) -> Result<MockResponse> {
572 if response.delay > 0 {
574 tokio::time::sleep(std::time::Duration::from_millis(response.delay)).await;
575 }
576
577 let mut processed_response = response.clone();
579
580 if let Some(env_manager) = &self.environment_manager {
582 if let Some(_env_vars) = self.get_environment_variables(context) {
583 processed_response.body = env_manager.substitute_variables(&response.body).value;
584 }
585 }
586
587 Ok(processed_response)
588 }
589
590 fn get_environment_variables(
592 &self,
593 context: &RequestExecutionContext,
594 ) -> Option<HashMap<String, String>> {
595 if let Some(env_manager) = &self.environment_manager {
596 if let Some(active_env) = env_manager.get_active_environment() {
597 return Some(active_env.variables.clone());
598 }
599 }
600
601 Some(context.environment_variables.clone())
602 }
603
604 pub fn get_request_metrics(&self, workspace: &Workspace) -> RequestMetrics {
606 let mut total_requests = 0u64;
607 let mut successful_requests = 0u64;
608 let mut failed_requests = 0u64;
609 let mut total_response_time = 0u64;
610 let mut request_counts = HashMap::new();
611 let mut last_execution: Option<DateTime<Utc>> = None;
612
613 for request in &workspace.requests {
615 total_requests += 1;
616
617 for response in &request.responses {
619 let execution_count = response.history.len() as u64;
620 *request_counts.entry(request.id.clone()).or_insert(0) += execution_count;
621
622 for entry in &response.history {
623 total_response_time += entry.duration_ms;
624
625 if let Some(current_last) = last_execution {
627 if entry.timestamp > current_last {
628 last_execution = Some(entry.timestamp);
629 }
630 } else {
631 last_execution = Some(entry.timestamp);
632 }
633
634 if entry.duration_ms < 5000 {
636 successful_requests += 1;
638 } else {
639 failed_requests += 1;
640 }
641 }
642 }
643 }
644
645 self.collect_folder_request_metrics(
647 &workspace.folders,
648 &mut total_requests,
649 &mut successful_requests,
650 &mut failed_requests,
651 &mut total_response_time,
652 &mut request_counts,
653 &mut last_execution,
654 );
655
656 let average_response_time = if total_requests > 0 {
657 total_response_time as f64 / total_requests as f64
658 } else {
659 0.0
660 };
661
662 let mut popular_requests: Vec<_> = request_counts.into_iter().collect();
664 popular_requests.sort_by(|a, b| b.1.cmp(&a.1));
665 popular_requests.truncate(5);
666
667 RequestMetrics {
668 total_requests,
669 successful_requests,
670 failed_requests,
671 average_response_time_ms: average_response_time,
672 popular_requests,
673 last_execution,
674 }
675 }
676
677 #[allow(clippy::only_used_in_recursion)]
679 #[allow(clippy::too_many_arguments)]
680 fn collect_folder_request_metrics(
681 &self,
682 folders: &[Folder],
683 total_requests: &mut u64,
684 successful_requests: &mut u64,
685 failed_requests: &mut u64,
686 total_response_time: &mut u64,
687 request_counts: &mut HashMap<EntityId, u64>,
688 last_execution: &mut Option<DateTime<Utc>>,
689 ) {
690 for folder in folders {
691 for request in &folder.requests {
692 *total_requests += 1;
693
694 for response in &request.responses {
696 let execution_count = response.history.len() as u64;
697 *request_counts.entry(request.id.clone()).or_insert(0) += execution_count;
698
699 for entry in &response.history {
700 *total_response_time += entry.duration_ms;
701
702 if let Some(current_last) = *last_execution {
704 if entry.timestamp > current_last {
705 *last_execution = Some(entry.timestamp);
706 }
707 } else {
708 *last_execution = Some(entry.timestamp);
709 }
710
711 if entry.duration_ms < 5000 {
713 *successful_requests += 1;
714 } else {
715 *failed_requests += 1;
716 }
717 }
718 }
719 }
720
721 self.collect_folder_request_metrics(
723 &folder.folders,
724 total_requests,
725 successful_requests,
726 failed_requests,
727 total_response_time,
728 request_counts,
729 last_execution,
730 );
731 }
732 }
733
734 pub fn create_route_from_request(&self, request: &MockRequest) -> Result<Route> {
736 if !request.enabled {
737 return Err(Error::validation("Request is disabled"));
738 }
739
740 let response = request
741 .active_response()
742 .ok_or_else(|| Error::validation("No active response found"))?;
743
744 let mut route = Route::new(request.method.clone(), request.url.clone());
746
747 route.metadata.insert("id".to_string(), serde_json::json!(request.id));
749 route.metadata.insert("response".to_string(), serde_json::json!(response.body));
750 route
751 .metadata
752 .insert("status_code".to_string(), serde_json::json!(response.status_code));
753 route.metadata.insert("headers".to_string(), serde_json::json!(request.headers));
754 route
755 .metadata
756 .insert("query_params".to_string(), serde_json::json!(request.query_params));
757 route.metadata.insert("enabled".to_string(), serde_json::json!(request.enabled));
758 route
759 .metadata
760 .insert("created_at".to_string(), serde_json::json!(request.created_at));
761 route
762 .metadata
763 .insert("updated_at".to_string(), serde_json::json!(request.updated_at));
764
765 Ok(route)
766 }
767
768 pub fn update_route_registry(
770 &self,
771 workspace: &Workspace,
772 route_registry: &mut RouteRegistry,
773 ) -> Result<()> {
774 route_registry.clear();
775
776 for request in &workspace.requests {
778 if request.enabled {
779 if let Ok(route) = self.create_route_from_request(request) {
780 let _ = route_registry.add_route(route);
781 }
782 }
783 }
784
785 self.add_folder_routes_to_registry(&workspace.folders, route_registry)?;
787
788 Ok(())
789 }
790
791 fn add_folder_routes_to_registry(
793 &self,
794 folders: &[Folder],
795 route_registry: &mut RouteRegistry,
796 ) -> Result<()> {
797 for folder in folders {
798 for request in &folder.requests {
799 if request.enabled {
800 if let Ok(route) = self.create_route_from_request(request) {
801 let _ = route_registry.add_route(route);
802 }
803 }
804 }
805
806 self.add_folder_routes_to_registry(&folder.folders, route_registry)?;
808 }
809
810 Ok(())
811 }
812
813 fn generate_response_cache_key(
817 &self,
818 request_id: &EntityId,
819 context: &RequestExecutionContext,
820 ) -> String {
821 use std::collections::hash_map::DefaultHasher;
822 use std::hash::{Hash, Hasher};
823
824 let mut hasher = DefaultHasher::new();
825 request_id.hash(&mut hasher);
826 context.workspace_id.hash(&mut hasher);
827
828 for (key, value) in &context.environment_variables {
830 key.hash(&mut hasher);
831 value.hash(&mut hasher);
832 }
833
834 for (key, value) in &context.global_headers {
836 key.hash(&mut hasher);
837 value.hash(&mut hasher);
838 }
839
840 format!("req_{}_{}", hasher.finish(), request_id)
841 }
842
843 async fn validate_request_cached(
845 &self,
846 request: &MockRequest,
847 context: &RequestExecutionContext,
848 ) -> Result<RequestValidationResult> {
849 if !self.optimizations_enabled {
850 return Ok(self.validate_request(request, context));
851 }
852
853 let cache_key = format!("val_{}_{}", request.id, context.workspace_id);
855
856 if let Some(cached_result) = self.validation_cache.get(&cache_key).await {
858 return Ok(cached_result);
859 }
860
861 let result = self.validate_request(request, context);
863
864 self.validation_cache.insert(cache_key, result.clone(), None).await;
866
867 Ok(result)
868 }
869
870 fn convert_mock_response_to_cached_response(&self, response: &MockResponse) -> CachedResponse {
872 CachedResponse {
873 status_code: response.status_code,
874 headers: response.headers.clone(),
875 body: response.body.clone(),
876 content_type: response.headers.get("Content-Type").cloned(),
877 }
878 }
879
880 fn convert_cached_response_to_mock_response(&self, cached: CachedResponse) -> MockResponse {
882 MockResponse {
883 id: EntityId::new(),
884 name: "Cached Response".to_string(),
885 status_code: cached.status_code,
886 headers: cached.headers,
887 body: cached.body,
888 delay: 0, active: true,
890 created_at: chrono::Utc::now(),
891 updated_at: chrono::Utc::now(),
892 history: Vec::new(),
893 intelligent: None,
894 drift: None,
895 }
896 }
897
898 pub async fn get_performance_summary(&self) -> crate::performance::PerformanceSummary {
900 self.performance_monitor.get_summary().await
901 }
902
903 pub async fn get_cache_stats(&self) -> (crate::cache::CacheStats, crate::cache::CacheStats) {
905 let response_cache_stats = self.response_cache.stats().await;
906 let validation_cache_stats = self.validation_cache.stats().await;
907 (response_cache_stats, validation_cache_stats)
908 }
909
910 pub async fn clear_caches(&self) {
912 self.response_cache.get_response("").await; self.validation_cache.clear().await;
914 }
915}
916
917impl Default for RequestProcessor {
918 fn default() -> Self {
919 Self::new()
920 }
921}