mlua 0.11.6

High level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau with async/await features and support of writing native Lua modules in Rust.
Documentation
use std::io::Result as IoResult;
use std::result::Result as StdResult;

use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};

fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
    lua.load(r#"return require(...)"#).call(path)
}

fn run_require_pcall(lua: &Lua, path: impl IntoLua) -> Result<MultiValue> {
    lua.load(r#"return pcall(require, ...)"#).call(path)
}

#[track_caller]
fn get_value<V: FromLua>(value: &Value, key: impl IntoLua) -> V {
    value.as_table().unwrap().get(key).unwrap()
}

#[track_caller]
fn get_str(value: &Value, key: impl IntoLua) -> String {
    get_value(value, key)
}

#[test]
fn test_require_errors() {
    let lua = Lua::new();

    // RequireAbsolutePath
    let res = run_require(&lua, "/an/absolute/path");
    assert!(res.is_err());
    assert!(
        (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
    );

    // RequireUnprefixedPath
    let res = run_require(&lua, "an/unprefixed/path");
    assert!(res.is_err());
    assert!(
        (res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
    );

    // Pass non-string to require
    let res = run_require(&lua, true);
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string())
        .contains("bad argument #1 to 'require' (string expected, got boolean)"));

    // Require from loadstring
    let res = lua
        .load(r#"return loadstring("require('./a/relative/path')")()"#)
        .eval::<Value>();
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string()).contains("require is not supported in this context"));

    // RequireAliasThatDoesNotExist
    let res = run_require(&lua, "@this.alias.does.not.exist");
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias"));

    // IllegalAlias
    let res = run_require(&lua, "@");
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));

    // Test throwing mlua::Error
    struct MyRequire(TextRequirer);

    impl Require for MyRequire {
        fn is_require_allowed(&self, chunk_name: &str) -> bool {
            self.0.is_require_allowed(chunk_name)
        }

        fn reset(&mut self, _chunk_name: &str) -> StdResult<(), NavigateError> {
            Err(Error::runtime("test error"))?
        }

        fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
            self.0.jump_to_alias(path)
        }

        fn to_parent(&mut self) -> StdResult<(), NavigateError> {
            self.0.to_parent()
        }

        fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
            self.0.to_child(name)
        }

        fn has_module(&self) -> bool {
            self.0.has_module()
        }

        fn cache_key(&self) -> String {
            self.0.cache_key()
        }

        fn has_config(&self) -> bool {
            self.0.has_config()
        }

        fn config(&self) -> IoResult<Vec<u8>> {
            self.0.config()
        }

        fn loader(&self, lua: &Lua) -> Result<mlua::Function> {
            self.0.loader(lua)
        }
    }

    let require = lua
        .create_require_function(MyRequire(TextRequirer::new()))
        .unwrap();
    lua.globals().set("require", require).unwrap();
    let res = lua.load(r#"return require('./a/relative/path')"#).exec();
    assert!((res.unwrap_err().to_string()).contains("test error"));
}

#[test]
fn test_require_without_config() {
    let lua = Lua::new();

    // RequireSimpleRelativePath
    let res = run_require(&lua, "./tests/luau/require/without_config/dependency").unwrap();
    assert_eq!("result from dependency", get_str(&res, 1));

    // RequireSimpleRelativePathWithinPcall
    let res = run_require_pcall(&lua, "./tests/luau/require/without_config/dependency").unwrap();
    assert!(res[0].as_boolean().unwrap());
    assert_eq!("result from dependency", get_str(&res[1], 1));

    // RequireRelativeToRequiringFile
    let res = run_require(&lua, "./tests/luau/require/without_config/module").unwrap();
    assert_eq!("result from dependency", get_str(&res, 1));
    assert_eq!("required into module", get_str(&res, 2));

    // RequireLua
    let res = run_require(&lua, "./tests/luau/require/without_config/lua_dependency").unwrap();
    assert_eq!("result from lua_dependency", get_str(&res, 1));

    // RequireInitLuau
    let res = run_require(&lua, "./tests/luau/require/without_config/luau").unwrap();
    assert_eq!("result from init.luau", get_str(&res, 1));

    // RequireInitLua
    let res = run_require(&lua, "./tests/luau/require/without_config/lua").unwrap();
    assert_eq!("result from init.lua", get_str(&res, 1));

    // RequireSubmoduleUsingSelfIndirectly
    let res = run_require(&lua, "./tests/luau/require/without_config/nested_module_requirer").unwrap();
    assert_eq!("result from submodule", get_str(&res, 1));

    // RequireSubmoduleUsingSelfDirectly
    let res = run_require(&lua, "./tests/luau/require/without_config/nested").unwrap();
    assert_eq!("result from submodule", get_str(&res, 1));

    // CannotRequireInitLuauDirectly
    let res = run_require(&lua, "./tests/luau/require/without_config/nested/init");
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string()).contains("could not resolve child component \"init\""));

    // RequireNestedInits
    let res = run_require(&lua, "./tests/luau/require/without_config/nested_inits_requirer").unwrap();
    assert_eq!("result from nested_inits/init", get_str(&res, 1));
    assert_eq!("required into module", get_str(&res, 2));

    // RequireWithFileAmbiguity
    let res = run_require(
        &lua,
        "./tests/luau/require/without_config/ambiguous_file_requirer",
    );
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string())
        .contains("could not resolve child component \"dependency\" (ambiguous)"));

    // RequireWithDirectoryAmbiguity
    let res = run_require(
        &lua,
        "./tests/luau/require/without_config/ambiguous_directory_requirer",
    );
    assert!(res.is_err());
    assert!((res.unwrap_err().to_string())
        .contains("could not resolve child component \"dependency\" (ambiguous)"));

    // CheckCachedResult
    let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap();
    assert!(res.is_table());
}

fn test_require_with_config_inner(r#type: &str) {
    let lua = Lua::new();

    let base_path = format!("./tests/luau/require/{type}");

    // RequirePathWithAlias
    let res = run_require(&lua, format!("{base_path}/src/alias_requirer")).unwrap();
    assert_eq!("result from dependency", get_str(&res, 1));

    // RequirePathWithAlias (case-insensitive)
    let res2 = run_require(&lua, format!("{base_path}/src/alias_requirer_uc")).unwrap();
    assert_eq!("result from dependency", get_str(&res2, 1));
    assert_eq!(res.to_pointer(), res2.to_pointer());

    // RequirePathWithParentAlias
    let res = run_require(&lua, format!("{base_path}/src/parent_alias_requirer")).unwrap();
    assert_eq!("result from other_dependency", get_str(&res, 1));

    // RequirePathWithAliasPointingToDirectory
    let res = run_require(&lua, format!("{base_path}/src/directory_alias_requirer")).unwrap();
    assert_eq!("result from subdirectory_dependency", get_str(&res, 1));

    // RequireChainedAliasesSuccess
    let res = run_require(
        &lua,
        format!("{base_path}/chained_aliases/subdirectory/successful_requirer"),
    )
    .unwrap();
    assert_eq!("result from inner_dependency", get_str(&get_value(&res, 1), 1));
    assert_eq!("result from outer_dependency", get_str(&get_value(&res, 2), 1));

    // RequireChainedAliasesFailureCyclic
    let res = run_require(
        &lua,
        format!("{base_path}/chained_aliases/subdirectory/failing_requirer_cyclic"),
    );
    assert!(res.is_err());
    let err_msg = "error requiring module \"@cyclicentry\": detected alias cycle (@cyclic1 -> @cyclic2 -> @cyclic3 -> @cyclic1)";
    assert!(res.unwrap_err().to_string().contains(err_msg));

    // RequireChainedAliasesFailureMissing
    let res = run_require(
        &lua,
        format!("{base_path}/chained_aliases/subdirectory/failing_requirer_missing"),
    );
    assert!(res.is_err());
    let err_msg = "error requiring module \"@brokenchain\": @missing is not a valid alias";
    assert!(res.unwrap_err().to_string().contains(err_msg));
}

#[test]
fn test_require_with_config() {
    test_require_with_config_inner("with_config");
}

#[test]
fn test_require_with_config_luau() {
    test_require_with_config_inner("with_config_luau");
}

#[cfg(all(feature = "async", not(windows)))]
#[tokio::test]
async fn test_async_require() -> Result<()> {
    let lua = Lua::new();

    let temp_dir = tempfile::tempdir().unwrap();
    let temp_path = temp_dir.path().join("async_chunk.luau");
    std::fs::write(
        &temp_path,
        r#"
        sleep_ms(10)
        return "result_after_async_sleep"
    "#,
    )
    .unwrap();

    lua.globals().set(
        "sleep_ms",
        lua.create_async_function(|_, ms: u64| async move {
            tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
            Ok(())
        })?,
    )?;
    lua.globals().set("tmp_dir", temp_dir.path().to_str().unwrap())?;
    lua.globals().set(
        "curr_dir_components",
        std::env::current_dir().unwrap().components().count(),
    )?;

    lua.load(
        r#"
        local path_to_root = string.rep("/..", curr_dir_components - 1)
        local result = require(`.{path_to_root}{tmp_dir}/async_chunk`)
        assert(result == "result_after_async_sleep")
        "#,
    )
    .exec_async()
    .await
}