1use std::{error::Error as StdError, fmt, string::String as StdString, sync::Arc};
2
3use gc_arena::{Collect, Rootable};
4use thiserror::Error;
5
6use crate::{Callback, CallbackReturn, Context, MetaMethod, Singleton, Table, UserData, Value};
7
8#[derive(Debug, Clone, Copy, Error)]
9#[error("type error, expected {expected}, found {found}")]
10pub struct TypeError {
11 pub expected: &'static str,
12 pub found: &'static str,
13}
14
15#[derive(Debug, Copy, Clone, Collect)]
16#[collect(no_drop)]
17pub struct LuaError<'gc>(pub Value<'gc>);
18
19impl<'gc> fmt::Display for LuaError<'gc> {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 write!(f, "{}", self.0)
22 }
23}
24
25impl<'gc> From<Value<'gc>> for LuaError<'gc> {
26 fn from(error: Value<'gc>) -> Self {
27 LuaError(error)
28 }
29}
30
31impl<'gc> LuaError<'gc> {
32 pub fn to_static(self) -> StaticLuaError {
33 self.into()
34 }
35}
36
37#[derive(Debug, Clone, Error)]
38#[error("{0}")]
39pub struct StaticLuaError(StdString);
40
41impl<'gc> From<LuaError<'gc>> for StaticLuaError {
42 fn from(error: LuaError<'gc>) -> Self {
43 Self(error.to_string())
44 }
45}
46
47#[derive(Debug, Clone, Collect)]
48#[collect(require_static)]
49pub struct RuntimeError(pub Arc<anyhow::Error>);
50
51impl fmt::Display for RuntimeError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 self.0.fmt(f)
54 }
55}
56
57impl<E: Into<anyhow::Error>> From<E> for RuntimeError {
58 fn from(err: E) -> Self {
59 Self(Arc::new(err.into()))
60 }
61}
62
63impl RuntimeError {
64 pub fn root_cause(&self) -> &(dyn StdError + 'static) {
65 self.0.root_cause()
66 }
67
68 pub fn is<E>(&self) -> bool
69 where
70 E: fmt::Display + fmt::Debug + Send + Sync + 'static,
71 {
72 self.0.is::<E>()
73 }
74
75 pub fn downcast<E>(&self) -> Option<&E>
76 where
77 E: fmt::Display + fmt::Debug + Send + Sync + 'static,
78 {
79 self.0.downcast_ref::<E>()
80 }
81}
82
83impl AsRef<dyn StdError + 'static> for RuntimeError {
84 fn as_ref(&self) -> &(dyn StdError + 'static) {
85 (*self.0).as_ref()
86 }
87}
88
89#[derive(Debug, Clone, Collect)]
90#[collect(no_drop)]
91pub enum Error<'gc> {
92 Lua(LuaError<'gc>),
93 Runtime(RuntimeError),
94}
95
96impl<'gc> fmt::Display for Error<'gc> {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 Error::Lua(err) => write!(f, "lua error: {}", err),
100 Error::Runtime(err) => write!(f, "runtime error: {}", err),
101 }
102 }
103}
104
105impl<'gc> From<Value<'gc>> for Error<'gc> {
106 fn from(value: Value<'gc>) -> Self {
107 Self::from_value(value)
108 }
109}
110
111impl<'gc> From<LuaError<'gc>> for Error<'gc> {
112 fn from(error: LuaError<'gc>) -> Self {
113 Self::Lua(error)
114 }
115}
116
117impl<'gc> From<RuntimeError> for Error<'gc> {
118 fn from(error: RuntimeError) -> Self {
119 Self::Runtime(error)
120 }
121}
122
123impl<'gc, E> From<E> for Error<'gc>
124where
125 E: Into<anyhow::Error>,
126{
127 fn from(error: E) -> Self {
128 Self::Runtime(RuntimeError::from(error))
129 }
130}
131
132impl<'gc> Error<'gc> {
133 pub fn from_value(value: Value<'gc>) -> Self {
134 if let Value::UserData(ud) = value {
135 if let Ok(err) = ud.downcast_static::<RuntimeError>() {
136 return Error::Runtime(err.clone());
137 }
138 }
139
140 Error::Lua(value.into())
141 }
142
143 pub fn to_value(&self, ctx: Context<'gc>) -> Value<'gc> {
144 match self {
145 Error::Lua(err) => err.0,
146 Error::Runtime(err) => {
147 #[derive(Copy, Clone, Collect)]
148 #[collect(no_drop)]
149 struct UDMeta<'gc>(Table<'gc>);
150
151 impl<'gc> Singleton<'gc> for UDMeta<'gc> {
152 fn create(ctx: Context<'gc>) -> Self {
153 let table = Table::new(&ctx);
154 table
155 .set(
156 ctx,
157 MetaMethod::ToString,
158 Callback::from_fn(&ctx, |ctx, _, mut stack| {
159 let ud = stack.consume::<UserData>(ctx)?;
160 let error = ud.downcast_static::<RuntimeError>()?;
161 stack.replace(ctx, error.to_string());
162 Ok(CallbackReturn::Return)
163 }),
164 )
165 .unwrap();
166 Self(table)
167 }
168 }
169
170 let ud = UserData::new_static(&ctx, err.clone());
171 ud.set_metatable(&ctx, Some(ctx.singleton::<Rootable![UDMeta<'_>]>().0));
172 ud.into()
173 }
174 }
175 }
176
177 pub fn to_static(&self) -> StaticError {
178 self.clone().into_static()
179 }
180
181 pub fn into_static(self) -> StaticError {
182 self.into()
183 }
184}
185
186#[derive(Debug, Clone)]
187pub enum StaticError {
188 Lua(StaticLuaError),
189 Runtime(RuntimeError),
190}
191
192impl fmt::Display for StaticError {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 StaticError::Lua(err) => write!(f, "lua error: {err}"),
196 StaticError::Runtime(err) => write!(f, "runtime error: {err}"),
197 }
198 }
199}
200
201impl StdError for StaticError {
202 fn source(&self) -> Option<&(dyn StdError + 'static)> {
203 match self {
204 StaticError::Lua(err) => Some(err),
205 StaticError::Runtime(err) => Some(err.as_ref()),
206 }
207 }
208}
209
210impl From<StaticLuaError> for StaticError {
211 fn from(error: StaticLuaError) -> Self {
212 Self::Lua(error)
213 }
214}
215
216impl From<RuntimeError> for StaticError {
217 fn from(error: RuntimeError) -> Self {
218 Self::Runtime(error)
219 }
220}
221
222impl<'gc> From<Error<'gc>> for StaticError {
223 fn from(err: Error<'gc>) -> Self {
224 match err {
225 Error::Lua(err) => err.to_static().into(),
226 Error::Runtime(e) => e.into(),
227 }
228 }
229}