Skip to main content

autom8/output/
error.rs

1//! Error panel display.
2//!
3//! Provides detailed error display with formatted panels.
4
5use super::colors::*;
6
7const ERROR_PANEL_WIDTH: usize = 60;
8
9/// Structured error information for display.
10#[derive(Debug, Clone, PartialEq)]
11pub struct ErrorDetails {
12    /// Category of error (e.g., "Process Failed", "Timeout", "Auth Error")
13    pub error_type: String,
14    /// User-friendly description of what went wrong
15    pub message: String,
16    /// Exit code from subprocess, if applicable
17    pub exit_code: Option<i32>,
18    /// Stderr output from subprocess, if available
19    pub stderr: Option<String>,
20    /// Which Claude function failed
21    pub source: Option<String>,
22}
23
24impl ErrorDetails {
25    /// Create a new ErrorDetails instance.
26    pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
27        Self {
28            error_type: error_type.into(),
29            message: message.into(),
30            exit_code: None,
31            stderr: None,
32            source: None,
33        }
34    }
35
36    /// Set the exit code.
37    pub fn with_exit_code(mut self, code: i32) -> Self {
38        self.exit_code = Some(code);
39        self
40    }
41
42    /// Set the stderr output.
43    pub fn with_stderr(mut self, stderr: impl Into<String>) -> Self {
44        self.stderr = Some(stderr.into());
45        self
46    }
47
48    /// Set the source function.
49    pub fn with_source(mut self, source: impl Into<String>) -> Self {
50        self.source = Some(source.into());
51        self
52    }
53
54    /// Print this error using the error panel.
55    pub fn print_panel(&self) {
56        print_error_panel(
57            &self.error_type,
58            &self.message,
59            self.exit_code,
60            self.stderr.as_deref(),
61        );
62    }
63}
64
65impl std::fmt::Display for ErrorDetails {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "[{}] {}", self.error_type, self.message)?;
68
69        if let Some(source) = &self.source {
70            write!(f, " (source: {})", source)?;
71        }
72
73        if let Some(code) = self.exit_code {
74            write!(f, " [exit code: {}]", code)?;
75        }
76
77        if let Some(stderr) = &self.stderr {
78            let trimmed = stderr.trim();
79            if !trimmed.is_empty() {
80                if let Some(first_line) = trimmed.lines().next() {
81                    write!(f, " stderr: {}", first_line)?;
82                }
83            }
84        }
85
86        Ok(())
87    }
88}
89
90/// Print a dedicated error panel with full error details.
91pub fn print_error_panel(
92    error_type: &str,
93    message: &str,
94    exit_code: Option<i32>,
95    stderr: Option<&str>,
96) {
97    let top_border = format!("╔{}╗", "═".repeat(ERROR_PANEL_WIDTH - 2));
98    let bottom_border = format!("╚{}╝", "═".repeat(ERROR_PANEL_WIDTH - 2));
99    let separator = format!("╟{}╢", "─".repeat(ERROR_PANEL_WIDTH - 2));
100
101    println!("{RED}{BOLD}{}{RESET}", top_border);
102
103    let header = format!(" ERROR: {} ", error_type);
104    let header_padding = ERROR_PANEL_WIDTH.saturating_sub(header.len() + 2);
105    let left_pad = header_padding / 2;
106    let right_pad = header_padding - left_pad;
107    println!(
108        "{RED}{BOLD}║{}{}{}║{RESET}",
109        " ".repeat(left_pad),
110        header,
111        " ".repeat(right_pad)
112    );
113
114    println!("{RED}{}{RESET}", separator);
115
116    print_panel_content("Message", message);
117
118    if let Some(code) = exit_code {
119        print_panel_line(&format!("Exit code: {}", code));
120    }
121
122    if let Some(err) = stderr {
123        let trimmed = err.trim();
124        if !trimmed.is_empty() {
125            println!("{RED}{}{RESET}", separator);
126            print_panel_content("Stderr", trimmed);
127        }
128    }
129
130    println!("{RED}{BOLD}{}{RESET}", bottom_border);
131}
132
133fn print_panel_content(label: &str, content: &str) {
134    let max_content_width = ERROR_PANEL_WIDTH - 6;
135
136    print_panel_line(&format!("{}:", label));
137
138    for line in content.lines() {
139        if line.len() <= max_content_width {
140            print_panel_line(&format!("  {}", line));
141        } else {
142            let mut remaining = line;
143            while !remaining.is_empty() {
144                let (chunk, rest) = if remaining.len() <= max_content_width - 2 {
145                    (remaining, "")
146                } else {
147                    let break_at = remaining[..max_content_width - 2]
148                        .rfind(|c: char| c.is_whitespace() || c == '/' || c == '\\' || c == ':')
149                        .map(|i| i + 1)
150                        .unwrap_or(max_content_width - 2);
151                    (&remaining[..break_at], &remaining[break_at..])
152                };
153                print_panel_line(&format!("  {}", chunk));
154                remaining = rest;
155            }
156        }
157    }
158}
159
160fn print_panel_line(text: &str) {
161    let max_width = ERROR_PANEL_WIDTH - 4;
162    let display_text = if text.len() > max_width {
163        &text[..max_width]
164    } else {
165        text
166    };
167    let padding = max_width.saturating_sub(display_text.len());
168    println!(
169        "{RED}║{RESET} {}{} {RED}║{RESET}",
170        display_text,
171        " ".repeat(padding)
172    );
173}