mockforge_runtime_daemon/
detector.rs1use axum::{extract::Request, http::StatusCode, middleware::Next, response::Response};
7use std::sync::Arc;
8use tokio::sync::RwLock;
9use tracing::{debug, info, warn};
10
11use crate::auto_generator::AutoGenerator;
12use crate::config::RuntimeDaemonConfig;
13
14#[derive(Clone)]
16pub struct NotFoundDetector {
17 config: Arc<RuntimeDaemonConfig>,
19 generator: Arc<RwLock<Option<Arc<AutoGenerator>>>>,
21}
22
23impl NotFoundDetector {
24 pub fn new(config: RuntimeDaemonConfig) -> Self {
26 Self {
27 config: Arc::new(config),
28 generator: Arc::new(RwLock::new(None)),
29 }
30 }
31
32 pub async fn set_generator(&self, generator: Arc<AutoGenerator>) {
34 let mut gen = self.generator.write().await;
35 *gen = Some(generator);
36 }
37
38 fn should_exclude(&self, path: &str) -> bool {
40 self.config.exclude_patterns.iter().any(|pattern| {
41 if pattern.starts_with('/') {
42 path.starts_with(pattern)
43 } else {
44 path.contains(pattern)
45 }
46 })
47 }
48
49 pub async fn detect_and_auto_create(self, request: Request, next: Next) -> Response {
51 let method = request.method().clone();
53 let uri = request.uri().clone();
54 let path = uri.path().to_string();
55
56 let response = next.run(request).await;
58
59 if !self.config.enabled || !self.config.auto_create_on_404 {
61 return response;
62 }
63
64 if response.status() != StatusCode::NOT_FOUND {
66 return response;
67 }
68
69 if self.should_exclude(&path) {
71 debug!("Excluding path from auto-generation: {}", path);
72 return response;
73 }
74
75 info!("Detected 404 for {} {}, triggering auto-generation", method, path);
76
77 let generator = self.generator.read().await;
79 if let Some(ref gen) = *generator {
80 let method_str = method.to_string();
82 let path_str = path.clone();
83 let gen_clone = Arc::clone(gen);
84
85 tokio::spawn(async move {
87 if let Err(e) = gen_clone.generate_mock_from_404(&method_str, &path_str).await {
88 warn!("Failed to auto-generate mock for {} {}: {}", method_str, path_str, e);
89 } else {
90 info!("Successfully auto-generated mock for {} {}", method_str, path_str);
91 }
92 });
93 } else {
94 debug!("Auto-generator not available, skipping mock creation");
95 }
96
97 response
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[tokio::test]
106 async fn test_should_exclude() {
107 let config = RuntimeDaemonConfig {
108 exclude_patterns: vec!["/health".to_string(), "/metrics".to_string()],
109 ..Default::default()
110 };
111 let detector = NotFoundDetector::new(config);
112
113 assert!(detector.should_exclude("/health"));
114 assert!(detector.should_exclude("/health/check"));
115 assert!(detector.should_exclude("/metrics"));
116 assert!(!detector.should_exclude("/api/users"));
117 }
118}