use mlua::prelude::*;
use crate::config::Config;
use crate::policy::{FsAccess, PathOp};
pub(crate) fn check_path(lua: &Lua, path: &str, op: PathOp) -> LuaResult<FsAccess> {
let config = lua.app_data_ref::<Config>().ok_or_else(|| {
LuaError::external(
"mlua-batteries: Config not initialized (use register_all or register_all_with)",
)
})?;
config
.path_policy
.resolve(std::path::Path::new(path), op)
.map_err(LuaError::external)
}
#[cfg(feature = "http")]
pub(crate) fn check_url(lua: &Lua, url: &str, method: &str) -> LuaResult<()> {
let config = lua
.app_data_ref::<Config>()
.ok_or_else(|| LuaError::external("mlua-batteries: Config not initialized"))?;
config
.http_policy
.check_url(url, method)
.map_err(LuaError::external)
}
pub(crate) fn check_env_get(lua: &Lua, key: &str) -> LuaResult<()> {
let config = lua
.app_data_ref::<Config>()
.ok_or_else(|| LuaError::external("mlua-batteries: Config not initialized"))?;
config.env_policy.check_get(key).map_err(LuaError::external)
}
pub(crate) fn check_env_set(lua: &Lua, key: &str) -> LuaResult<()> {
let config = lua
.app_data_ref::<Config>()
.ok_or_else(|| LuaError::external("mlua-batteries: Config not initialized"))?;
config.env_policy.check_set(key).map_err(LuaError::external)
}
#[cfg(feature = "llm")]
pub(crate) fn check_llm_request(
lua: &Lua,
provider: &str,
model: &str,
base_url: &str,
) -> LuaResult<()> {
let config = lua
.app_data_ref::<Config>()
.ok_or_else(|| LuaError::external("mlua-batteries: Config not initialized"))?;
config
.llm_policy
.check_request(provider, model, base_url)
.map_err(LuaError::external)
}
pub(crate) fn with_config<T>(lua: &Lua, f: impl FnOnce(&Config) -> T) -> LuaResult<T> {
let config = lua
.app_data_ref::<Config>()
.ok_or_else(|| LuaError::external("mlua-batteries: Config not initialized"))?;
Ok(f(&config))
}
pub(crate) enum TableKind {
Array(usize),
Map(Vec<(LuaValue, LuaValue)>),
}
pub(crate) fn classify(table: &LuaTable) -> LuaResult<TableKind> {
let len = table.raw_len();
let pairs: Vec<(LuaValue, LuaValue)> = table
.pairs::<LuaValue, LuaValue>()
.collect::<LuaResult<Vec<_>>>()?;
let len_i64 = i64::try_from(len).unwrap_or(i64::MAX);
let is_array = len > 0
&& pairs.len() == len
&& pairs
.iter()
.all(|(k, _)| matches!(k, LuaValue::Integer(i) if *i >= 1 && *i <= len_i64));
if is_array {
Ok(TableKind::Array(len))
} else {
Ok(TableKind::Map(pairs))
}
}
#[cfg(test)]
pub(crate) fn test_eval<T: mlua::FromLua>(code: &str) -> T {
let lua = Lua::new();
crate::register_all(&lua, "std").unwrap();
lua.load(code).eval().unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_table_is_map() {
let lua = Lua::new();
let t: LuaTable = lua.load("return {}").eval().unwrap();
assert!(matches!(classify(&t).unwrap(), TableKind::Map(pairs) if pairs.is_empty()));
}
#[test]
fn contiguous_array() {
let lua = Lua::new();
let t: LuaTable = lua.load("return {10, 20, 30}").eval().unwrap();
assert!(matches!(classify(&t).unwrap(), TableKind::Array(3)));
}
#[test]
fn string_keyed_is_map() {
let lua = Lua::new();
let t: LuaTable = lua.load("return {a=1, b=2}").eval().unwrap();
assert!(matches!(classify(&t).unwrap(), TableKind::Map(pairs) if pairs.len() == 2));
}
#[test]
fn mixed_keys_is_map() {
let lua = Lua::new();
let t: LuaTable = lua.load("return {10, 20, a='x'}").eval().unwrap();
assert!(matches!(classify(&t).unwrap(), TableKind::Map(pairs) if pairs.len() == 3));
}
#[test]
fn sparse_int_keys_is_map() {
let lua = Lua::new();
let t: LuaTable = lua
.load("local t = {}; t[1] = 'a'; t[3] = 'c'; return t")
.eval()
.unwrap();
assert!(matches!(classify(&t).unwrap(), TableKind::Map(_)));
}
#[test]
fn single_element_array() {
let lua = Lua::new();
let t: LuaTable = lua.load("return {'only'}").eval().unwrap();
assert!(matches!(classify(&t).unwrap(), TableKind::Array(1)));
}
}