1use crate::enhanced_errors::PipelineError;
8use serde::{Deserialize, Serialize};
9use sklears_core::error::Result as SklResult;
10use std::collections::HashMap;
11use std::fmt;
12
13#[derive(Debug, Clone)]
15pub struct DeveloperFriendlyError {
16 pub original_error: PipelineError,
18 pub explanation: String,
20 pub fix_suggestions: Vec<FixSuggestion>,
22 pub documentation_links: Vec<String>,
24 pub code_examples: Vec<CodeExample>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct FixSuggestion {
31 pub priority: SuggestionPriority,
33 pub title: String,
35 pub description: String,
37 pub code_snippet: Option<String>,
39 pub estimated_effort_minutes: u32,
41}
42
43#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
45pub enum SuggestionPriority {
46 Critical,
48 High,
50 Medium,
52 Low,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct CodeExample {
59 pub title: String,
60 pub description: String,
61 pub code: String,
62 pub expected_output: String,
63}
64
65#[derive(Debug)]
67pub struct PipelineDebugger {
68 pub session_id: String,
70 pub state: DebugState,
72 pub breakpoints: Vec<Breakpoint>,
74 pub execution_trace: Vec<TraceEntry>,
76 pub watch_expressions: HashMap<String, WatchExpression>,
78}
79
80#[derive(Debug, Clone)]
82pub enum DebugState {
83 Idle,
85 Running,
87 Paused {
89 breakpoint_id: String,
90 context: ExecutionContext,
91 },
92 Stepping { step_type: StepType },
94 Error { error: String },
96}
97
98#[derive(Debug, Clone)]
100pub enum StepType {
101 StepNext,
103 StepInto,
105 StepOut,
107 Continue,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct Breakpoint {
114 pub id: String,
116 pub location: String,
118 pub condition: Option<String>,
120 pub enabled: bool,
122 pub hit_count: u32,
124}
125
126#[derive(Debug, Clone)]
128pub struct ExecutionContext {
129 pub current_component: String,
131 pub stage: String,
133 pub variables: HashMap<String, String>,
135 pub input_shape: Option<(usize, usize)>,
137 pub memory_usage_mb: f64,
139 pub execution_time_ms: f64,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct TraceEntry {
146 pub timestamp: u64,
148 pub component: String,
150 pub operation: String,
152 pub duration_ms: f64,
154 pub input_shape: Option<(usize, usize)>,
156 pub output_shape: Option<(usize, usize)>,
158 pub memory_before_mb: f64,
160 pub memory_after_mb: f64,
162 pub notes: Vec<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct WatchExpression {
169 pub expression: String,
171 pub current_value: Option<String>,
173 pub value_history: Vec<(u64, String)>, pub active: bool,
177}
178
179impl PipelineDebugger {
180 #[must_use]
182 pub fn new() -> Self {
183 Self {
184 session_id: format!("debug-{}", chrono::Utc::now().timestamp()),
185 state: DebugState::Idle,
186 breakpoints: Vec::new(),
187 execution_trace: Vec::new(),
188 watch_expressions: HashMap::new(),
189 }
190 }
191
192 pub fn start_debug_session(&mut self) -> SklResult<()> {
194 self.state = DebugState::Running;
195 self.execution_trace.clear();
196 println!("π Debug session started: {}", self.session_id);
197 Ok(())
198 }
199
200 pub fn add_breakpoint(&mut self, location: String, condition: Option<String>) -> String {
202 let id = format!("bp-{}", self.breakpoints.len());
203 let breakpoint = Breakpoint {
204 id: id.clone(),
205 location,
206 condition,
207 enabled: true,
208 hit_count: 0,
209 };
210 self.breakpoints.push(breakpoint);
211 println!(
212 "π΄ Breakpoint added: {} at {}",
213 id,
214 self.breakpoints
215 .last()
216 .map(|b| b.location.as_str())
217 .unwrap_or("unknown")
218 );
219 id
220 }
221
222 pub fn add_watch(&mut self, name: String, expression: String) {
224 let watch = WatchExpression {
225 expression: expression.clone(),
226 current_value: None,
227 value_history: Vec::new(),
228 active: true,
229 };
230 self.watch_expressions.insert(name.clone(), watch);
231 println!("ποΈ Watch added: {name} -> {expression}");
232 }
233
234 pub fn record_trace(&mut self, entry: TraceEntry) {
236 self.execution_trace.push(entry);
237
238 let current_component = self
240 .execution_trace
241 .last()
242 .map(|t| t.component.clone())
243 .unwrap_or_default();
244 if let Some(breakpoint) = self.check_breakpoints(¤t_component) {
245 self.pause_at_breakpoint(breakpoint);
246 }
247 }
248
249 fn check_breakpoints(&mut self, current_component: &str) -> Option<String> {
251 for breakpoint in &mut self.breakpoints {
252 if breakpoint.enabled && breakpoint.location == current_component {
253 breakpoint.hit_count += 1;
254 return Some(breakpoint.id.clone());
255 }
256 }
257 None
258 }
259
260 fn pause_at_breakpoint(&mut self, breakpoint_id: String) {
262 let context = ExecutionContext {
263 current_component: "example_component".to_string(), stage: "example_stage".to_string(),
265 variables: HashMap::new(),
266 input_shape: Some((100, 10)),
267 memory_usage_mb: 128.5,
268 execution_time_ms: 1500.0,
269 };
270
271 self.state = DebugState::Paused {
272 breakpoint_id: breakpoint_id.clone(),
273 context,
274 };
275 println!("βΈοΈ Paused at breakpoint: {breakpoint_id}");
276 }
277
278 pub fn get_debug_summary(&self) -> DebugSummary {
280 DebugSummary {
281 session_id: self.session_id.clone(),
282 total_trace_entries: self.execution_trace.len(),
283 active_breakpoints: self.breakpoints.iter().filter(|bp| bp.enabled).count(),
284 active_watches: self
285 .watch_expressions
286 .iter()
287 .filter(|(_, w)| w.active)
288 .count(),
289 current_state: format!("{:?}", self.state),
290 total_execution_time_ms: self.execution_trace.iter().map(|e| e.duration_ms).sum(),
291 peak_memory_usage_mb: self
292 .execution_trace
293 .iter()
294 .map(|e| e.memory_after_mb)
295 .fold(0.0, f64::max),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct DebugSummary {
303 pub session_id: String,
304 pub total_trace_entries: usize,
305 pub active_breakpoints: usize,
306 pub active_watches: usize,
307 pub current_state: String,
308 pub total_execution_time_ms: f64,
309 pub peak_memory_usage_mb: f64,
310}
311
312impl Default for PipelineDebugger {
313 fn default() -> Self {
314 Self::new()
315 }
316}
317
318pub struct ErrorMessageEnhancer;
320
321impl ErrorMessageEnhancer {
322 #[must_use]
324 pub fn enhance_error(error: PipelineError) -> DeveloperFriendlyError {
325 match &error {
326 PipelineError::ConfigurationError { message, .. } => {
327 Self::enhance_configuration_error(error.clone(), message)
328 }
329 PipelineError::DataCompatibilityError {
330 expected,
331 actual,
332 stage,
333 ..
334 } => Self::enhance_data_compatibility_error(error.clone(), expected, actual, stage),
335 PipelineError::StructureError {
336 error_type,
337 affected_components,
338 ..
339 } => Self::enhance_structure_error(error.clone(), error_type, affected_components),
340 PipelineError::PerformanceWarning {
341 warning_type,
342 impact_level,
343 ..
344 } => Self::enhance_performance_warning(error.clone(), warning_type, impact_level),
345 PipelineError::ResourceError {
346 resource_type,
347 limit,
348 current,
349 component,
350 ..
351 } => Self::enhance_resource_error(
352 error.clone(),
353 resource_type,
354 *limit,
355 *current,
356 component,
357 ),
358 PipelineError::TypeSafetyError {
359 violation_type,
360 expected_type,
361 actual_type,
362 ..
363 } => Self::enhance_type_safety_error(
364 error.clone(),
365 violation_type,
366 expected_type,
367 actual_type,
368 ),
369 }
370 }
371
372 fn enhance_configuration_error(error: PipelineError, message: &str) -> DeveloperFriendlyError {
373 DeveloperFriendlyError {
374 original_error: error,
375 explanation: format!(
376 "Configuration Error: {message}\n\n\
377 This error occurs when pipeline parameters are incorrectly configured. \
378 Common causes include invalid parameter values, missing required parameters, \
379 or incompatible parameter combinations."
380 ),
381 fix_suggestions: vec![
382 FixSuggestion {
383 priority: SuggestionPriority::Critical,
384 title: "Check parameter documentation".to_string(),
385 description: "Review the documentation for valid parameter ranges and types".to_string(),
386 code_snippet: Some("pipeline.builder().with_parameter(\"valid_value\").build()".to_string()),
387 estimated_effort_minutes: 5,
388 },
389 FixSuggestion {
390 priority: SuggestionPriority::High,
391 title: "Use configuration validation".to_string(),
392 description: "Enable configuration validation to catch errors early".to_string(),
393 code_snippet: Some("pipeline.validate_config().expect(\"Valid configuration\")".to_string()),
394 estimated_effort_minutes: 2,
395 },
396 ],
397 documentation_links: vec![
398 "https://docs.sklears.com/pipeline-configuration".to_string(),
399 "https://docs.sklears.com/parameter-validation".to_string(),
400 ],
401 code_examples: vec![
402 CodeExample {
403 title: "Basic Pipeline Configuration".to_string(),
404 description: "Example of properly configuring a pipeline".to_string(),
405 code: "use sklears_compose::Pipeline;\n\nlet pipeline = Pipeline::builder()\n .add_step(\"scaler\", StandardScaler::new())\n .add_step(\"model\", LinearRegression::new())\n .build()?;".to_string(),
406 expected_output: "Successfully configured pipeline with 2 steps".to_string(),
407 },
408 ],
409 }
410 }
411
412 fn enhance_data_compatibility_error(
413 error: PipelineError,
414 expected: &crate::enhanced_errors::DataShape,
415 actual: &crate::enhanced_errors::DataShape,
416 stage: &str,
417 ) -> DeveloperFriendlyError {
418 DeveloperFriendlyError {
419 original_error: error,
420 explanation: format!(
421 "Data Compatibility Error at stage '{}'\n\n\
422 Expected: {} samples Γ {} features ({})\n\
423 Actual: {} samples Γ {} features ({})\n\n\
424 This error occurs when data shapes don't match between pipeline stages. \
425 This is often caused by incorrect data preprocessing or feature selection.",
426 stage,
427 expected.samples, expected.features, expected.data_type,
428 actual.samples, actual.features, actual.data_type
429 ),
430 fix_suggestions: vec![
431 FixSuggestion {
432 priority: SuggestionPriority::Critical,
433 title: "Check data preprocessing".to_string(),
434 description: "Verify that preprocessing steps maintain expected data shapes".to_string(),
435 code_snippet: Some("print(\"Data shape after preprocessing: {}\", X.shape)".to_string()),
436 estimated_effort_minutes: 10,
437 },
438 FixSuggestion {
439 priority: SuggestionPriority::High,
440 title: "Add shape validation".to_string(),
441 description: "Add explicit shape validation between pipeline stages".to_string(),
442 code_snippet: Some("pipeline.add_validation_step(ShapeValidator::new(expected_shape))".to_string()),
443 estimated_effort_minutes: 5,
444 },
445 ],
446 documentation_links: vec![
447 "https://docs.sklears.com/data-shapes".to_string(),
448 "https://docs.sklears.com/pipeline-validation".to_string(),
449 ],
450 code_examples: vec![
451 CodeExample {
452 title: "Data Shape Debugging".to_string(),
453 description: "How to debug data shape issues".to_string(),
454 code: "// Check data shapes at each step\nlet mut pipeline = Pipeline::new();\npipeline.debug_mode(true);\nlet result = pipeline.fit_transform(&X, &y)?;\nprintln!(\"Final shape: {:?}\", result.shape());".to_string(),
455 expected_output: "Detailed shape information at each step".to_string(),
456 },
457 ],
458 }
459 }
460
461 fn enhance_structure_error(
462 error: PipelineError,
463 error_type: &crate::enhanced_errors::StructureErrorType,
464 affected_components: &[String],
465 ) -> DeveloperFriendlyError {
466 DeveloperFriendlyError {
467 original_error: error,
468 explanation: format!(
469 "Pipeline Structure Error: {:?}\n\
470 Affected components: {}\n\n\
471 This error indicates a problem with the pipeline's structure or component relationships.",
472 error_type,
473 affected_components.join(", ")
474 ),
475 fix_suggestions: vec![
476 FixSuggestion {
477 priority: SuggestionPriority::Critical,
478 title: "Review pipeline structure".to_string(),
479 description: "Check the logical flow and dependencies between components".to_string(),
480 code_snippet: Some("pipeline.visualize_structure()".to_string()),
481 estimated_effort_minutes: 15,
482 },
483 ],
484 documentation_links: vec![
485 "https://docs.sklears.com/pipeline-structure".to_string(),
486 ],
487 code_examples: vec![],
488 }
489 }
490
491 fn enhance_performance_warning(
492 error: PipelineError,
493 warning_type: &crate::enhanced_errors::PerformanceWarningType,
494 impact_level: &crate::enhanced_errors::ImpactLevel,
495 ) -> DeveloperFriendlyError {
496 DeveloperFriendlyError {
497 original_error: error,
498 explanation: format!(
499 "Performance Warning: {warning_type:?} (Impact: {impact_level:?})\n\n\
500 This warning indicates potential performance issues that may affect execution."
501 ),
502 fix_suggestions: vec![FixSuggestion {
503 priority: SuggestionPriority::Medium,
504 title: "Profile pipeline performance".to_string(),
505 description: "Use the built-in profiler to identify bottlenecks".to_string(),
506 code_snippet: Some("pipeline.enable_profiling().run_with_profiling()".to_string()),
507 estimated_effort_minutes: 20,
508 }],
509 documentation_links: vec![
510 "https://docs.sklears.com/performance-optimization".to_string()
511 ],
512 code_examples: vec![],
513 }
514 }
515
516 fn enhance_resource_error(
517 error: PipelineError,
518 resource_type: &crate::enhanced_errors::ResourceType,
519 limit: f64,
520 current: f64,
521 component: &str,
522 ) -> DeveloperFriendlyError {
523 DeveloperFriendlyError {
524 original_error: error,
525 explanation: format!(
526 "Resource Constraint Error in component '{component}'\n\
527 Resource: {resource_type:?}\n\
528 Limit: {limit:.2}\n\
529 Current usage: {current:.2}\n\n\
530 The component is exceeding available resources."
531 ),
532 fix_suggestions: vec![FixSuggestion {
533 priority: SuggestionPriority::High,
534 title: "Optimize memory usage".to_string(),
535 description:
536 "Consider using streaming or batch processing to reduce memory footprint"
537 .to_string(),
538 code_snippet: Some("pipeline.set_batch_size(1000).enable_streaming()".to_string()),
539 estimated_effort_minutes: 30,
540 }],
541 documentation_links: vec!["https://docs.sklears.com/memory-optimization".to_string()],
542 code_examples: vec![],
543 }
544 }
545
546 fn enhance_type_safety_error(
547 error: PipelineError,
548 violation_type: &crate::enhanced_errors::TypeViolationType,
549 expected_type: &str,
550 actual_type: &str,
551 ) -> DeveloperFriendlyError {
552 DeveloperFriendlyError {
553 original_error: error,
554 explanation: format!(
555 "Type Safety Error: {violation_type:?}\n\
556 Expected type: {expected_type}\n\
557 Actual type: {actual_type}\n\n\
558 This error occurs when there's a type mismatch between pipeline components."
559 ),
560 fix_suggestions: vec![FixSuggestion {
561 priority: SuggestionPriority::Critical,
562 title: "Check type compatibility".to_string(),
563 description: "Ensure all pipeline components have compatible input/output types"
564 .to_string(),
565 code_snippet: Some("pipeline.validate_type_safety()".to_string()),
566 estimated_effort_minutes: 10,
567 }],
568 documentation_links: vec!["https://docs.sklears.com/type-safety".to_string()],
569 code_examples: vec![],
570 }
571 }
572}
573
574impl fmt::Display for DeveloperFriendlyError {
576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
577 writeln!(f, "π¨ {}", self.explanation)?;
578
579 if !self.fix_suggestions.is_empty() {
580 writeln!(f, "\nπ‘ Suggested fixes:")?;
581 for (i, suggestion) in self.fix_suggestions.iter().enumerate() {
582 writeln!(
583 f,
584 " {}. [{}] {} (β{}min)",
585 i + 1,
586 match suggestion.priority {
587 SuggestionPriority::Critical => "π΄ CRITICAL",
588 SuggestionPriority::High => "π‘ HIGH",
589 SuggestionPriority::Medium => "π΅ MEDIUM",
590 SuggestionPriority::Low => "βͺ LOW",
591 },
592 suggestion.title,
593 suggestion.estimated_effort_minutes
594 )?;
595 writeln!(f, " {}", suggestion.description)?;
596 if let Some(code) = &suggestion.code_snippet {
597 writeln!(f, " Example: {code}")?;
598 }
599 }
600 }
601
602 if !self.documentation_links.is_empty() {
603 writeln!(f, "\nπ Helpful documentation:")?;
604 for link in &self.documentation_links {
605 writeln!(f, " β’ {link}")?;
606 }
607 }
608
609 Ok(())
610 }
611}
612
613impl fmt::Display for SuggestionPriority {
614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615 match self {
616 SuggestionPriority::Critical => write!(f, "Critical"),
617 SuggestionPriority::High => write!(f, "High"),
618 SuggestionPriority::Medium => write!(f, "Medium"),
619 SuggestionPriority::Low => write!(f, "Low"),
620 }
621 }
622}
623
624#[allow(non_snake_case)]
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::enhanced_errors::*;
629
630 #[test]
631 fn test_debugger_creation() {
632 let debugger = PipelineDebugger::new();
633 assert!(matches!(debugger.state, DebugState::Idle));
634 assert_eq!(debugger.breakpoints.len(), 0);
635 assert_eq!(debugger.execution_trace.len(), 0);
636 }
637
638 #[test]
639 fn test_add_breakpoint() {
640 let mut debugger = PipelineDebugger::new();
641 let bp_id = debugger.add_breakpoint("test_component".to_string(), None);
642
643 assert_eq!(debugger.breakpoints.len(), 1);
644 assert_eq!(debugger.breakpoints[0].id, bp_id);
645 assert_eq!(debugger.breakpoints[0].location, "test_component");
646 assert!(debugger.breakpoints[0].enabled);
647 }
648
649 #[test]
650 fn test_add_watch() {
651 let mut debugger = PipelineDebugger::new();
652 debugger.add_watch("test_var".to_string(), "x.shape".to_string());
653
654 assert_eq!(debugger.watch_expressions.len(), 1);
655 assert!(debugger.watch_expressions.contains_key("test_var"));
656 assert_eq!(debugger.watch_expressions["test_var"].expression, "x.shape");
657 }
658
659 #[test]
660 fn test_error_enhancement() {
661 let error = PipelineError::ConfigurationError {
662 message: "Invalid parameter value".to_string(),
663 suggestions: vec!["Check documentation".to_string()],
664 context: ErrorContext {
665 pipeline_stage: "test".to_string(),
666 component_name: "test_component".to_string(),
667 input_shape: Some((100, 10)),
668 parameters: std::collections::HashMap::new(),
669 stack_trace: vec![],
670 },
671 };
672
673 let enhanced = ErrorMessageEnhancer::enhance_error(error);
674 assert!(enhanced.explanation.contains("Configuration Error"));
675 assert!(!enhanced.fix_suggestions.is_empty());
676 assert!(enhanced.fix_suggestions[0].priority as u8 <= SuggestionPriority::Critical as u8);
677 }
678
679 #[test]
680 fn test_debug_summary() {
681 let debugger = PipelineDebugger::new();
682 let summary = debugger.get_debug_summary();
683
684 assert_eq!(summary.total_trace_entries, 0);
685 assert_eq!(summary.active_breakpoints, 0);
686 assert_eq!(summary.active_watches, 0);
687 assert_eq!(summary.total_execution_time_ms, 0.0);
688 }
689}