1use super::colors::*;
6
7const ERROR_PANEL_WIDTH: usize = 60;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct ErrorDetails {
12 pub error_type: String,
14 pub message: String,
16 pub exit_code: Option<i32>,
18 pub stderr: Option<String>,
20 pub source: Option<String>,
22}
23
24impl ErrorDetails {
25 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 pub fn with_exit_code(mut self, code: i32) -> Self {
38 self.exit_code = Some(code);
39 self
40 }
41
42 pub fn with_stderr(mut self, stderr: impl Into<String>) -> Self {
44 self.stderr = Some(stderr.into());
45 self
46 }
47
48 pub fn with_source(mut self, source: impl Into<String>) -> Self {
50 self.source = Some(source.into());
51 self
52 }
53
54 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
90pub 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}