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