use crate::debug::{DebugConfig, DebugFileConfig};
use crate::error::ConfigError;
use crate::loader::{LuaConfig, default_libs};
use mlua::{Function, Lua, Result as LuaResult, StdLib, Value};
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub libs: Vec<String>,
pub debug: DebugConfig,
}
impl RuntimeConfig {
pub fn new() -> Self {
Self {
libs: default_libs(),
debug: DebugConfig::default(),
}
}
pub fn full() -> Self {
Self {
libs: vec![
"std_all_unsafe".into(),
"assertions".into(),
"testing".into(),
"env".into(),
"regex".into(),
"json".into(),
"yaml".into(),
],
debug: DebugConfig::default(),
}
}
pub fn minimal() -> Self {
Self {
libs: vec!["std_all".into()],
debug: DebugConfig::default(),
}
}
pub fn from_libs(libs: Vec<String>) -> Self {
Self {
libs,
debug: DebugConfig::default(),
}
}
pub fn with_debug(mut self, debug: DebugConfig) -> Self {
self.debug = debug;
self
}
pub fn with_debug_from_file_and_env(self, file: Option<&DebugFileConfig>) -> Self {
self.with_debug(DebugConfig::from_env(file))
}
pub fn to_stdlib(&self) -> Result<StdLib, ConfigError> {
let mut additions = StdLib::NONE;
let mut subtractions = StdLib::NONE;
for lib in &self.libs {
let (is_subtraction, name) = if let Some(stripped) = lib.strip_prefix('-') {
(true, stripped)
} else {
(false, lib.as_str())
};
if !name.starts_with("std_") {
continue;
}
let flag = Self::parse_std_lib(name)?;
if is_subtraction {
subtractions |= flag;
} else {
additions |= flag;
}
}
let intersection = additions & subtractions;
Ok(additions ^ intersection)
}
fn parse_std_lib(name: &str) -> Result<StdLib, ConfigError> {
match name {
"std_all" => Ok(StdLib::ALL_SAFE),
"std_all_unsafe" => Ok(StdLib::ALL),
"std_coroutine" => Ok(StdLib::NONE),
"std_table" => Ok(StdLib::TABLE),
"std_io" => Ok(StdLib::IO),
"std_os" => Ok(StdLib::OS),
"std_string" => Ok(StdLib::STRING),
"std_math" => Ok(StdLib::MATH),
"std_package" => Ok(StdLib::PACKAGE),
"std_debug" => Ok(StdLib::DEBUG),
"std_jit" => Ok(StdLib::JIT),
"std_ffi" => Ok(StdLib::FFI),
"std_bit" => Ok(StdLib::BIT),
_ => Err(ConfigError::UnknownLibrary(name.to_string())),
}
}
pub fn should_enable_module(&self, module: &str) -> bool {
let has_positive = self.libs.iter().any(|lib| lib == module);
let has_negative = self
.libs
.iter()
.any(|lib| lib.strip_prefix('-') == Some(module));
has_positive && !has_negative
}
pub fn validate_and_warn(&self) {
let has_std_debug = self.libs.iter().any(|lib| lib == "std_debug");
let has_std_all_unsafe = self.libs.iter().any(|lib| lib == "std_all_unsafe");
let debug_subtracted = self
.libs
.iter()
.any(|lib| lib == "-std_debug" || lib == "-std_all_unsafe");
if (has_std_debug || has_std_all_unsafe) && !debug_subtracted {
if has_std_all_unsafe {
tracing::warn!(
"Unsafe Lua libraries enabled: std_all_unsafe. \
This includes std_debug which provides access to Lua internals. \
Not recommended for production."
);
} else {
tracing::warn!(
"Unsafe Lua library enabled: std_debug. \
Provides access to Lua internals and stack manipulation. \
Not recommended for production."
);
}
}
if self.should_enable_module("env") {
tracing::warn!(
"Security-sensitive module enabled: env. \
Provides filesystem and environment variable access."
);
}
tracing::debug!(libs = ?self.libs, "Lua library configuration");
}
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self::new()
}
}
impl From<LuaConfig> for RuntimeConfig {
fn from(config: LuaConfig) -> Self {
Self {
libs: config.libs,
debug: DebugConfig::default(),
}
}
}
pub fn lua_require(lua: &Lua, module_name: &str) -> LuaResult<Value> {
let require: Function = lua.globals().get("require")?;
require.call(module_name)
}