Skip to main content

luaur_rt/
error.rs

1//! Error type and `Result` alias, mirroring mlua's [`Error`] / [`Result`].
2//!
3//! We expose the common, developer-facing subset of mlua's `Error` variants
4//! (`RuntimeError`, `SyntaxError`, the two conversion errors, etc.) plus the
5//! `Error::external` / `Error::runtime` constructors. Variants specific to
6//! features we have not implemented (async, serde, scopes, registry keys) are
7//! intentionally omitted.
8
9use std::error::Error as StdError;
10use std::fmt;
11use std::sync::Arc;
12
13// `Result` is defined below; `Arc<Error>` is used by `Error::CallbackError`.
14
15/// A boxed standard error, used by [`Error::ExternalError`].
16type DynStdError = dyn StdError + Send + Sync;
17
18/// The result type used throughout `luaur-rt`, mirroring [`mlua::Result`].
19pub type Result<T> = std::result::Result<T, Error>;
20
21/// Errors that can occur when interacting with the Lua engine.
22///
23/// The variant set mirrors the commonly used part of mlua's `Error`. It is
24/// marked `#[non_exhaustive]` (like mlua's) so new variants can be added
25/// without a breaking change.
26#[derive(Debug, Clone)]
27#[non_exhaustive]
28pub enum Error {
29    /// A Lua syntax (compile/parse) error.
30    SyntaxError {
31        /// The human-readable message produced by the compiler.
32        message: String,
33        /// Whether the input looked like it was merely incomplete (e.g. an
34        /// unterminated block). Always `false` for now; reserved for REPL use.
35        incomplete_input: bool,
36    },
37    /// A Lua runtime error (`error(..)`, a failed `assert`, a type error, or a
38    /// Rust callback returning `Err`).
39    RuntimeError(String),
40    /// A memory allocation error reported by the VM.
41    MemoryError(String),
42    /// A value could not be converted **from** a Lua value into the requested
43    /// Rust type.
44    FromLuaConversionError {
45        /// The Lua type name of the source value.
46        from: &'static str,
47        /// The name of the target Rust type.
48        to: String,
49        /// Optional extra detail.
50        message: Option<String>,
51    },
52    /// A Rust value could not be converted **into** a Lua value.
53    ToLuaConversionError {
54        /// The name of the source Rust type.
55        from: &'static str,
56        /// The Lua type name being targeted.
57        to: &'static str,
58        /// Optional extra detail.
59        message: Option<String>,
60    },
61    /// A `UserData` value was accessed as the wrong concrete type.
62    UserDataTypeMismatch,
63    /// A `UserData` value was used after it had been destructed (dropped).
64    UserDataDestructed,
65    /// Either a callback or a userdata method was called, but the callback or
66    /// userdata had been destructed.
67    ///
68    /// This happens when a function/userdata created via [`Lua::scope`] is used
69    /// after the scope has ended (so the scope has already dropped the boxed
70    /// closure / invalidated the Lua object). Mirrors
71    /// `mlua::Error::CallbackDestructed`.
72    ///
73    /// [`Lua::scope`]: crate::Lua::scope
74    CallbackDestructed,
75    /// A Rust callback returned `Err`, which was raised as a Lua error and then
76    /// caught at a protected-call boundary (e.g. [`Function::call`]). The
77    /// original error is preserved in `cause`. Mirrors
78    /// `mlua::Error::CallbackError`.
79    ///
80    /// luaur-rt only produces this variant for callback errors that carry
81    /// structured meaning across the Lua boundary (currently
82    /// [`Error::CallbackDestructed`] and [`Error::UserDataDestructed`]); plain
83    /// string callback errors continue to surface as [`Error::RuntimeError`] for
84    /// backward compatibility.
85    ///
86    /// [`Function::call`]: crate::Function::call
87    CallbackError {
88        /// A Lua call-stack traceback (empty when luaur-rt does not capture one).
89        traceback: String,
90        /// The original error returned by the Rust callback.
91        cause: Arc<Error>,
92    },
93    /// A `UserData` could not be immutably borrowed because it is already
94    /// mutably borrowed.
95    UserDataBorrowError,
96    /// A `UserData` could not be mutably borrowed because it is already
97    /// borrowed.
98    UserDataBorrowMutError,
99    /// A coroutine ([`crate::Thread`]) could not be resumed because it has
100    /// finished, errored, or is currently running. Mirrors
101    /// `mlua::Error::CoroutineUnresumable`.
102    CoroutineUnresumable,
103    /// A [`crate::RegistryKey`] was used with a [`crate::Lua`] that does not
104    /// own it. Mirrors `mlua::Error::MismatchedRegistryKey`.
105    MismatchedRegistryKey,
106    /// A `create_function_mut` / `create_userdata` mutable callback was invoked
107    /// re-entrantly while a previous invocation still held the `&mut`. The inner
108    /// `RefCell` borrow failed, which we surface as this variant rather than
109    /// allowing mutable aliasing. Mirrors `mlua::Error::RecursiveMutCallback`.
110    RecursiveMutCallback,
111    /// A Rust panic was raised across a `pcall` boundary, caught and resumed
112    /// once; a later attempt to re-raise/observe it failed because the panic was
113    /// already resumed. Mirrors `mlua::Error::PreviouslyResumedPanic`.
114    PreviouslyResumedPanic,
115    /// An error originating outside Lua, wrapped via [`Error::external`].
116    ExternalError(Arc<DynStdError>),
117    /// A serialization (Rust -> Lua) error produced by the `serde` feature.
118    /// Mirrors `mlua::Error::SerializeError`.
119    #[cfg(feature = "serde")]
120    SerializeError(String),
121    /// A deserialization (Lua -> Rust) error produced by the `serde` feature.
122    /// Mirrors `mlua::Error::DeserializeError`.
123    #[cfg(feature = "serde")]
124    DeserializeError(String),
125    /// One or more static type-checker diagnostics produced by the `typecheck`
126    /// feature (e.g. [`Lua::check`](crate::Lua::check) /
127    /// [`Chunk::check`](crate::Chunk::check)). Each
128    /// [`TypeDiagnostic`](crate::TypeDiagnostic) carries its 1-based source
129    /// location.
130    ///
131    /// There is no mlua equivalent: Lua has no static types, so mlua cannot
132    /// type-check a script before running it.
133    #[cfg(feature = "typecheck")]
134    TypeError(Vec<crate::TypeDiagnostic>),
135}
136
137#[cfg(feature = "serde")]
138impl serde::ser::Error for Error {
139    fn custom<T: fmt::Display>(msg: T) -> Self {
140        Error::SerializeError(msg.to_string())
141    }
142}
143
144#[cfg(feature = "serde")]
145impl serde::de::Error for Error {
146    fn custom<T: fmt::Display>(msg: T) -> Self {
147        Error::DeserializeError(msg.to_string())
148    }
149}
150
151impl Error {
152    /// Create a [`Error::RuntimeError`] from any displayable message.
153    ///
154    /// Mirrors `mlua::Error::runtime`.
155    pub fn runtime<S: fmt::Display>(message: S) -> Self {
156        Error::RuntimeError(message.to_string())
157    }
158
159    /// Try to view the wrapped external error as a concrete type `T`.
160    ///
161    /// Mirrors the common `mlua::Error::downcast_ref` use: only
162    /// [`Error::ExternalError`] carries a wrapped error to downcast.
163    pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
164        match self {
165            Error::ExternalError(e) => e.downcast_ref::<T>(),
166            _ => None,
167        }
168    }
169
170    /// Wrap an arbitrary `std::error::Error` as an [`Error::ExternalError`].
171    ///
172    /// Mirrors `mlua::Error::external`: if the input is already a luaur
173    /// [`Error`], it is preserved as-is rather than re-wrapped.
174    pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
175        let boxed: Box<DynStdError> = err.into();
176        // Preserve an already-`Error` value instead of nesting it.
177        match boxed.downcast::<Error>() {
178            Ok(e) => *e,
179            Err(other) => Error::ExternalError(other.into()),
180        }
181    }
182}
183
184impl fmt::Display for Error {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        match self {
187            Error::SyntaxError { message, .. } => write!(f, "syntax error: {message}"),
188            Error::RuntimeError(msg) => write!(f, "runtime error: {msg}"),
189            Error::MemoryError(msg) => write!(f, "memory error: {msg}"),
190            Error::FromLuaConversionError { from, to, message } => {
191                write!(f, "error converting Lua {from} to {to}")?;
192                if let Some(m) = message {
193                    write!(f, " ({m})")?;
194                }
195                Ok(())
196            }
197            Error::ToLuaConversionError { from, to, message } => {
198                write!(f, "error converting {from} to Lua {to}")?;
199                if let Some(m) = message {
200                    write!(f, " ({m})")?;
201                }
202                Ok(())
203            }
204            Error::UserDataTypeMismatch => write!(f, "userdata type mismatch"),
205            Error::UserDataDestructed => write!(f, "userdata used after being destructed"),
206            Error::CallbackDestructed => write!(
207                f,
208                "a destructed callback or destructed userdata method was called"
209            ),
210            Error::CallbackError { cause, .. } => write!(f, "{cause}"),
211            Error::UserDataBorrowError => write!(f, "userdata already mutably borrowed"),
212            Error::UserDataBorrowMutError => write!(f, "userdata already borrowed"),
213            Error::CoroutineUnresumable => write!(f, "cannot resume this coroutine"),
214            Error::MismatchedRegistryKey => {
215                write!(f, "registry key used with the wrong Lua instance")
216            }
217            Error::RecursiveMutCallback => {
218                write!(f, "mutable callback called recursively")
219            }
220            Error::PreviouslyResumedPanic => {
221                write!(f, "previously resumed panic returned again")
222            }
223            Error::ExternalError(err) => write!(f, "{err}"),
224            #[cfg(feature = "serde")]
225            Error::SerializeError(msg) => write!(f, "serialize error: {msg}"),
226            #[cfg(feature = "serde")]
227            Error::DeserializeError(msg) => write!(f, "deserialize error: {msg}"),
228            #[cfg(feature = "typecheck")]
229            Error::TypeError(diagnostics) => {
230                write!(f, "type error(s):")?;
231                for d in diagnostics {
232                    write!(f, "\n  {d}")?;
233                }
234                Ok(())
235            }
236        }
237    }
238}
239
240impl StdError for Error {
241    fn source(&self) -> Option<&(dyn StdError + 'static)> {
242        match self {
243            Error::ExternalError(err) => Some(&**err),
244            Error::CallbackError { cause, .. } => Some(&**cause),
245            _ => None,
246        }
247    }
248}
249
250impl From<std::io::Error> for Error {
251    fn from(err: std::io::Error) -> Self {
252        Error::external(err)
253    }
254}
255
256impl From<&str> for Error {
257    fn from(msg: &str) -> Self {
258        Error::RuntimeError(msg.to_string())
259    }
260}
261
262impl From<String> for Error {
263    fn from(msg: String) -> Self {
264        Error::RuntimeError(msg)
265    }
266}
267
268/// Convenience for turning an arbitrary error/displayable into an [`Error`].
269///
270/// Mirrors `mlua::ExternalError`. `&str`/`String` become a [`Error::RuntimeError`]
271/// (matching mlua's runtime-error semantics for string errors); other
272/// `std::error::Error` types become an [`Error::ExternalError`].
273pub trait ExternalError {
274    /// Convert `self` into an [`Error`].
275    fn into_lua_err(self) -> Error;
276}
277
278impl<E: Into<Box<DynStdError>>> ExternalError for E {
279    fn into_lua_err(self) -> Error {
280        // `&str`/`String`/`io::Error`/... all implement `Into<Box<dyn Error>>`.
281        // Plain string errors become runtime errors (matching mlua); a wrapped
282        // `mlua::Error` is preserved by `Error::external`.
283        Error::external(self)
284    }
285}
286
287/// `Result` extension mirroring `mlua::ExternalResult`: lift any
288/// `Result<T, E>` into a `luaur` [`Result`] by converting the error.
289pub trait ExternalResult<T> {
290    /// Convert the error side via [`ExternalError::into_lua_err`].
291    fn into_lua_err(self) -> Result<T>;
292}
293
294impl<T, E: ExternalError> ExternalResult<T> for std::result::Result<T, E> {
295    fn into_lua_err(self) -> Result<T> {
296        self.map_err(ExternalError::into_lua_err)
297    }
298}