1use regex_lite::Regex;
12use serde::{Deserialize, Serialize};
13use std::collections::HashSet;
14
15#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
17pub struct RuntimeSignal {
18 pub signal_id: String,
20 pub signal_type: RuntimeSignalType,
22 pub content: String,
24 pub confidence: f32,
26 pub location: Option<String>,
28 pub metadata: serde_json::Value,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
34#[serde(rename_all = "snake_case")]
35pub enum RuntimeSignalType {
36 CompilerDiagnostic,
38 RuntimePanic,
40 Timeout,
42 TestFailure,
44 PerformanceIssue,
46 ResourceExhaustion,
48 ConfigError,
50 SecurityIssue,
52 GenericError,
54}
55
56impl RuntimeSignal {
57 pub fn new(signal_type: RuntimeSignalType, content: String, confidence: f32) -> Self {
59 Self {
60 signal_id: uuid::Uuid::new_v4().to_string(),
61 signal_type,
62 content,
63 confidence,
64 location: None,
65 metadata: serde_json::json!({}),
66 }
67 }
68
69 pub fn with_location(mut self, location: String) -> Self {
71 self.location = Some(location);
72 self
73 }
74
75 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
77 self.metadata = metadata;
78 self
79 }
80}
81
82pub struct CompilerDiagnosticsParser {
84 rustc_error_patterns: Vec<(Regex, RuntimeSignalType)>,
86 rustc_warning_patterns: Vec<(Regex, RuntimeSignalType)>,
87}
88
89impl CompilerDiagnosticsParser {
90 pub fn new() -> Self {
92 let rustc_error_patterns = vec![
93 (
95 Regex::new(r"error\[E\d+\]:\s*(.+)").unwrap(),
96 RuntimeSignalType::CompilerDiagnostic,
97 ),
98 (
99 Regex::new(r"^(?i)borrow checker error").unwrap(),
100 RuntimeSignalType::CompilerDiagnostic,
101 ),
102 (
103 Regex::new(r"^(?i)cannot find (function|struct|module|trait|macro)").unwrap(),
104 RuntimeSignalType::CompilerDiagnostic,
105 ),
106 (
107 Regex::new(r"^(?i)unresolved (import|item)").unwrap(),
108 RuntimeSignalType::CompilerDiagnostic,
109 ),
110 (
111 Regex::new(r"^(?i)type mismatch").unwrap(),
112 RuntimeSignalType::CompilerDiagnostic,
113 ),
114 (
115 Regex::new(r"^(?i)mismatched types").unwrap(),
116 RuntimeSignalType::CompilerDiagnostic,
117 ),
118 (
119 Regex::new(r"^(?i)expected (struct|enum|tuple|array)").unwrap(),
120 RuntimeSignalType::CompilerDiagnostic,
121 ),
122 (
123 Regex::new(r"^(?i)trait bound.*not satisfied").unwrap(),
124 RuntimeSignalType::CompilerDiagnostic,
125 ),
126 (
127 Regex::new(r"^(?i)the trait.*is not implemented").unwrap(),
128 RuntimeSignalType::CompilerDiagnostic,
129 ),
130 (
131 Regex::new(r"^(?i)method.*not found").unwrap(),
132 RuntimeSignalType::CompilerDiagnostic,
133 ),
134 (
135 Regex::new(r"^(?i)no method named.*found").unwrap(),
136 RuntimeSignalType::CompilerDiagnostic,
137 ),
138 (
139 Regex::new(r"^(?i)cannot borrow.*as.*mutable").unwrap(),
140 RuntimeSignalType::CompilerDiagnostic,
141 ),
142 (
143 Regex::new(r"^(?i)cannot borrow.*as.*immutable").unwrap(),
144 RuntimeSignalType::CompilerDiagnostic,
145 ),
146 (
147 Regex::new(r"^(?i)lifetime.*required").unwrap(),
148 RuntimeSignalType::CompilerDiagnostic,
149 ),
150 ];
151
152 let rustc_warning_patterns = vec![
153 (
154 Regex::new(r"warning\[W\d+\]:\s*(.+)").unwrap(),
155 RuntimeSignalType::CompilerDiagnostic,
156 ),
157 (
158 Regex::new(r"^(?i)unused (import|variable|function|struct)").unwrap(),
159 RuntimeSignalType::CompilerDiagnostic,
160 ),
161 (
162 Regex::new(r"^(?i)dead code").unwrap(),
163 RuntimeSignalType::CompilerDiagnostic,
164 ),
165 (
166 Regex::new(r"^(?i)field is never read").unwrap(),
167 RuntimeSignalType::CompilerDiagnostic,
168 ),
169 (
170 Regex::new(r"^(?i)deprecated").unwrap(),
171 RuntimeSignalType::CompilerDiagnostic,
172 ),
173 ];
174
175 Self {
176 rustc_error_patterns,
177 rustc_warning_patterns,
178 }
179 }
180
181 pub fn parse(&self, output: &str) -> Vec<RuntimeSignal> {
183 let mut signals = Vec::new();
184
185 for line in output.lines() {
186 for (pattern, signal_type) in &self.rustc_error_patterns {
188 if let Some(caps) = pattern.captures(line) {
189 let content = caps.get(1).map(|m| m.as_str()).unwrap_or(line);
190 signals.push(RuntimeSignal::new(
191 signal_type.clone(),
192 format!("compiler_error: {}", content),
193 0.9,
194 ));
195 break;
196 }
197 }
198
199 for (pattern, signal_type) in &self.rustc_warning_patterns {
201 if let Some(caps) = pattern.captures(line) {
202 let content = caps.get(1).map(|m| m.as_str()).unwrap_or(line);
203 signals.push(RuntimeSignal::new(
204 signal_type.clone(),
205 format!("compiler_warning: {}", content),
206 0.6,
207 ));
208 break;
209 }
210 }
211 }
212
213 signals
214 }
215}
216
217impl Default for CompilerDiagnosticsParser {
218 fn default() -> Self {
219 Self::new()
220 }
221}
222
223pub struct StackTraceParser {
225 stack_trace_pattern: Regex,
227 panic_pattern: Regex,
229}
230
231impl StackTraceParser {
232 pub fn new() -> Self {
234 Self {
235 stack_trace_pattern: Regex::new(r"(?m)^\s+at\s+(.+?)(?:\s+in\s+(.+?))?(?:\s+\(.*\))?$")
236 .unwrap(),
237 panic_pattern: Regex::new(r"(?i)(thread\s+.*\s+panicked|panicked\s+at)").unwrap(),
238 }
239 }
240
241 pub fn parse(&self, output: &str) -> Vec<RuntimeSignal> {
243 let mut signals = Vec::new();
244
245 for line in output.lines() {
247 if self.panic_pattern.is_match(line) {
248 signals.push(RuntimeSignal::new(
249 RuntimeSignalType::RuntimePanic,
250 format!("panic: {}", line.trim()),
251 0.95,
252 ));
253 }
254 }
255
256 for cap in self.stack_trace_pattern.captures_iter(output) {
258 if let Some(location) = cap.get(1) {
259 let location_str = location.as_str().to_string();
260 let confidence = if location_str.contains("main") || location_str.contains("bin") {
261 0.9
262 } else {
263 0.7
264 };
265
266 signals.push(RuntimeSignal::new(
267 RuntimeSignalType::RuntimePanic,
268 format!("stack_frame: {}", location_str),
269 confidence,
270 ));
271 }
272 }
273
274 signals
275 }
276}
277
278impl Default for StackTraceParser {
279 fn default() -> Self {
280 Self::new()
281 }
282}
283
284pub struct LogAnalyzer {
286 timeout_patterns: Vec<Regex>,
288 resource_patterns: Vec<Regex>,
290 test_failure_patterns: Vec<Regex>,
292}
293
294impl LogAnalyzer {
295 pub fn new() -> Self {
297 Self {
298 timeout_patterns: vec![
299 Regex::new(r"(?i)timeout").unwrap(),
300 Regex::new(r"(?i)timed out").unwrap(),
301 Regex::new(r"(?i)deadline exceeded").unwrap(),
302 ],
303 resource_patterns: vec![
304 Regex::new(r"(?i)(out of memory|oom)").unwrap(),
305 Regex::new(r"(?i)memory allocation failed").unwrap(),
306 Regex::new(r"(?i)disk (full|space)").unwrap(),
307 Regex::new(r"(?i)too many open files").unwrap(),
308 Regex::new(r"(?i)resource temporarily unavailable").unwrap(),
309 ],
310 test_failure_patterns: vec![
311 Regex::new(r"(?i)test failed").unwrap(),
312 Regex::new(r"(?i)assertion failed").unwrap(),
313 Regex::new(r"(?i)expected .* but got").unwrap(),
314 Regex::new(r"(?i)panicked at").unwrap(),
315 ],
316 }
317 }
318
319 pub fn analyze(&self, logs: &str) -> Vec<RuntimeSignal> {
321 let mut signals = Vec::new();
322
323 for line in logs.lines() {
324 for pattern in &self.timeout_patterns {
326 if pattern.is_match(line) {
327 signals.push(RuntimeSignal::new(
328 RuntimeSignalType::Timeout,
329 format!("timeout: {}", line.trim()),
330 0.85,
331 ));
332 break;
333 }
334 }
335
336 for pattern in &self.resource_patterns {
338 if pattern.is_match(line) {
339 signals.push(RuntimeSignal::new(
340 RuntimeSignalType::ResourceExhaustion,
341 format!("resource: {}", line.trim()),
342 0.9,
343 ));
344 break;
345 }
346 }
347
348 for pattern in &self.test_failure_patterns {
350 if pattern.is_match(line) {
351 signals.push(RuntimeSignal::new(
352 RuntimeSignalType::TestFailure,
353 format!("test_failure: {}", line.trim()),
354 0.9,
355 ));
356 break;
357 }
358 }
359 }
360
361 signals.into_iter().collect()
362 }
363}
364
365impl Default for LogAnalyzer {
366 fn default() -> Self {
367 Self::new()
368 }
369}
370
371pub struct RuntimeSignalExtractor {
373 compiler_parser: CompilerDiagnosticsParser,
374 stack_trace_parser: StackTraceParser,
375 log_analyzer: LogAnalyzer,
376}
377
378impl RuntimeSignalExtractor {
379 pub fn new() -> Self {
381 Self {
382 compiler_parser: CompilerDiagnosticsParser::new(),
383 stack_trace_parser: StackTraceParser::new(),
384 log_analyzer: LogAnalyzer::new(),
385 }
386 }
387
388 pub fn extract_from_compiler(&self, output: &str) -> Vec<RuntimeSignal> {
390 self.compiler_parser.parse(output)
391 }
392
393 pub fn extract_from_stack_trace(&self, output: &str) -> Vec<RuntimeSignal> {
395 self.stack_trace_parser.parse(output)
396 }
397
398 pub fn extract_from_logs(&self, logs: &str) -> Vec<RuntimeSignal> {
400 self.log_analyzer.analyze(logs)
401 }
402
403 pub fn extract_all(
405 &self,
406 compiler_output: Option<&str>,
407 stack_trace: Option<&str>,
408 logs: Option<&str>,
409 ) -> Vec<RuntimeSignal> {
410 let mut all_signals = Vec::new();
411
412 if let Some(output) = compiler_output {
413 all_signals.extend(self.extract_from_compiler(output));
414 }
415
416 if let Some(trace) = stack_trace {
417 all_signals.extend(self.extract_from_stack_trace(trace));
418 }
419
420 if let Some(log) = logs {
421 all_signals.extend(self.extract_from_logs(log));
422 }
423
424 let mut seen = HashSet::new();
426 all_signals.retain(|s| seen.insert(s.content.clone()));
427
428 all_signals
429 }
430}
431
432impl Default for RuntimeSignalExtractor {
433 fn default() -> Self {
434 Self::new()
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_compiler_error_parsing() {
444 let parser = CompilerDiagnosticsParser::new();
445
446 let output = r#"
447error[E0425]: cannot find function `foo` in this scope
448 --> src/main.rs:10:5
449 |
45010 | foo();
451 | ^^^ not found in this scope
452
453error[E0308]: mismatched types
454"#;
455
456 let signals = parser.parse(output);
457 assert!(!signals.is_empty());
458 assert!(signals
459 .iter()
460 .any(|s| s.content.contains("cannot find function")));
461 }
462
463 #[test]
464 fn test_stack_trace_parsing() {
465 let parser = StackTraceParser::new();
466
467 let trace = r#"
468thread 'main' panicked at 'something failed', src/main.rs:10:5
469stack backtrace:
470 0: <core::panic::unwind_safe>::{{closure}}
471 1: std::panicking::{{closure}}
472 2: main::foo
473"#;
474
475 let signals = parser.parse(trace);
476 assert!(!signals.is_empty());
477 }
478
479 #[test]
480 fn test_log_analysis() {
481 let analyzer = LogAnalyzer::new();
482
483 let logs = r#"
484[INFO] Starting application
485[ERROR] Connection timeout after 30s
486[ERROR] Test case 'test_foo' failed: assertion failed
487"#;
488
489 let signals = analyzer.analyze(logs);
490 assert!(!signals.is_empty());
491 }
492
493 #[test]
494 fn test_runtime_signal_extractor() {
495 let extractor = RuntimeSignalExtractor::new();
496
497 let signals = extractor.extract_all(
498 Some("error[E0425]: cannot find function"),
499 Some("thread 'main' panicked"),
500 Some("connection timeout"),
501 );
502
503 assert!(signals.len() >= 3);
504 }
505}