lune_utils/fmt/error/
components.rs1use std::{
2 fmt,
3 str::FromStr,
4 sync::{Arc, LazyLock},
5};
6
7use console::style;
8use mlua::prelude::*;
9
10use super::StackTrace;
11
12static STYLED_STACK_BEGIN: LazyLock<String> = LazyLock::new(|| {
13 format!(
14 "{}{}{}",
15 style("[").dim(),
16 style("Stack Begin").blue(),
17 style("]").dim()
18 )
19});
20
21static STYLED_STACK_END: LazyLock<String> = LazyLock::new(|| {
22 format!(
23 "{}{}{}",
24 style("[").dim(),
25 style("Stack End").blue(),
26 style("]").dim()
27 )
28});
29
30const STACK_TRACE_INDENT: &str = " ";
34
35#[derive(Debug, Default, Clone)]
51pub struct ErrorComponents {
52 messages: Vec<String>,
53 trace: Option<StackTrace>,
54}
55
56impl ErrorComponents {
57 #[must_use]
61 pub fn messages(&self) -> &[String] {
62 &self.messages
63 }
64
65 #[must_use]
69 pub fn trace(&self) -> Option<&StackTrace> {
70 self.trace.as_ref()
71 }
72
73 #[must_use]
79 pub fn has_trace(&self) -> bool {
80 self.trace
81 .as_ref()
82 .is_some_and(|trace| !trace.lines().is_empty())
83 }
84}
85
86impl fmt::Display for ErrorComponents {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 for message in self.messages() {
89 writeln!(f, "{message}")?;
90 }
91 if self.has_trace() {
92 let trace = self.trace.as_ref().expect("trace exists and is non-empty");
93 writeln!(f, "{}", *STYLED_STACK_BEGIN)?;
94 for line in trace.lines() {
95 writeln!(f, "{STACK_TRACE_INDENT}{line}")?;
96 }
97 writeln!(f, "{}", *STYLED_STACK_END)?;
98 }
99 Ok(())
100 }
101}
102
103impl From<LuaError> for ErrorComponents {
104 fn from(error: LuaError) -> Self {
105 fn lua_error_message(e: &LuaError) -> String {
106 if let LuaError::RuntimeError(s) = e {
107 s.to_string()
108 } else {
109 e.to_string()
110 }
111 }
112
113 fn lua_stack_trace(source: &str) -> Option<StackTrace> {
114 StackTrace::from_str(source).ok()
117 }
118
119 #[allow(clippy::arc_with_non_send_sync)]
122 let mut error = Arc::new(error);
123 let mut messages = Vec::new();
124 while let LuaError::WithContext {
125 ref context,
126 ref cause,
127 } = *error
128 {
129 messages.push(context.to_string());
130 error = cause.clone();
131 }
132
133 let mut trace = if let LuaError::CallbackError {
135 ref traceback,
136 ref cause,
137 } = *error
138 {
139 messages.push(lua_error_message(cause));
140 lua_stack_trace(traceback)
141 } else if let LuaError::RuntimeError(ref s) = *error {
142 if let Some(pos) = s.find("stack traceback:") {
145 let (message, traceback) = s.split_at(pos);
146 messages.push(message.trim().to_string());
147 lua_stack_trace(traceback)
148 } else {
149 messages.push(s.to_string());
150 None
151 }
152 } else {
153 messages.push(lua_error_message(&error));
154 None
155 };
156
157 if let Some(trace) = &mut trace {
161 let lines = trace.lines_mut();
162 loop {
163 let first_is_c_and_empty = lines
164 .first()
165 .is_some_and(|line| line.source().is_c() && line.is_empty());
166 let second_is_c_and_nonempty = lines
167 .get(1)
168 .is_some_and(|line| line.source().is_c() && !line.is_empty());
169 if first_is_c_and_empty && second_is_c_and_nonempty {
170 lines.remove(0);
171 } else {
172 break;
173 }
174 }
175 }
176
177 if let Some(message) = messages.last_mut()
180 && let Some(line) = trace
181 .iter()
182 .flat_map(StackTrace::lines)
183 .find(|line| line.source().is_lua())
184 {
185 if let Some(path) = line.path() {
186 let prefix = format!("[string \"{path}\"]:");
187 if message.starts_with(&prefix) {
188 *message = message[prefix.len()..].trim().to_string();
189 }
190 }
191 if let Some(line) = line.line_number() {
192 let prefix = format!("{line}:");
193 if message.starts_with(&prefix) {
194 *message = message[prefix.len()..].trim().to_string();
195 }
196 }
197 }
198
199 ErrorComponents { messages, trace }
200 }
201}
202
203impl From<Box<LuaError>> for ErrorComponents {
204 fn from(value: Box<LuaError>) -> Self {
205 Self::from(*value)
206 }
207}