piccolo/
error.rs

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}