config: convert the setup argument with FromLua
The serde layer cannot ride mlua conversion automatically, but a
hand-written impl can: it runs inside the callback trampoline where
the main chunk frame is alive, so conversion errors keep the config
call site that the inline from_value call used to add.
Assisted-by: GLM-5.3 via pi
diff --git a/src/parallax.rs b/src/parallax.rs
index 63c2c52..f6e3333 100644
--- a/src/parallax.rs
+++ b/src/parallax.rs
@@ -4,6 +4,7 @@ use std::mem;
use mlua::AnyUserData;
use mlua::ErrorContext as _;
+use mlua::FromLua;
use mlua::Function;
use mlua::Lua;
use mlua::LuaSerdeExt as _;
@@ -29,13 +30,10 @@ struct Registrations {
}
impl Registrations {
- fn setup(&mut self, lua: &Lua, table: Table) -> Result<(), mlua::Error> {
+ fn setup(&mut self, lua: &Lua, setup: Setup) -> Result<(), mlua::Error> {
if self.setup.is_some() {
return Err(error_at(lua, "setup must be called exactly once"));
}
- let setup: Setup = lua
- .from_value(Value::Table(table))
- .map_err(|error| error_at(lua, &error_message(&error)))?;
if setup.variants.is_empty() {
return Err(error_at(lua, "variants must not be empty"));
}
@@ -65,6 +63,16 @@ impl Registrations {
/// — with no self to bind a userdata method to.
impl UserData for Registrations {}
+/// Converts `(setup {...})`'s argument, keeping the config call site in
+/// the error: `from_lua` runs inside the callback trampoline, so the
+/// main chunk's frame is still on the Lua stack for caller_position.
+impl FromLua for Setup {
+ fn from_lua(value: Value, lua: &Lua) -> Result<Setup, mlua::Error> {
+ lua.from_value(value)
+ .map_err(|error| error_at(lua, &error_message(&error)))
+ }
+}
+
/// A loaded parallax: the executable form of a config. The server
/// function and flow are owned handles into the embedded Lua state,
/// which stays alive in this struct; fields are ordered so the handles
@@ -156,8 +164,8 @@ fn install_module(lua: &Lua) -> Result<AnyUserData, mlua::Error> {
let setup = {
let module = module.clone();
- lua.create_function(move |lua, table: Table| {
- module.borrow_mut::<Registrations>()?.setup(lua, table)
+ lua.create_function(move |lua, setup: Setup| {
+ module.borrow_mut::<Registrations>()?.setup(lua, setup)
})?
};
let server = {
diff --git a/tests/cli.rs b/tests/cli.rs
index 38ab3b9..66f26c5 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -99,6 +99,25 @@ fn setup_errors_point_at_the_setup_call() {
);
}
+#[test]
+fn setup_conversion_errors_point_at_the_setup_call() {
+ let (_dir, path) = config_file(concat!(
+ "(local {: setup : flow : server} (require :parallax))\n",
+ "\n",
+ "(setup {:variants {:desktop {:viewport [1280 900]}} :extra 1})\n",
+ "\n",
+ "(server (fn [] {:cmd [\"true\"]}))\n",
+ "(flow (fn [] nil))\n",
+ ));
+ cargo_bin_cmd!("parallax")
+ .arg("list")
+ .arg("--config")
+ .arg(&path)
+ .assert()
+ .failure()
+ .stderr(predicate::str::contains("config.fnl:3:").and(predicate::str::contains("unknown")));
+}
+
#[test]
fn missing_setup_registration_fails() {
let (_dir, path) = config_file(concat!(