use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use mlua::{Lua, Value as LuaValue};
use serde_json::Value as JsonValue;
use crate::error::DbError;
use crate::scripting::conversion::json_to_lua;
use super::ScriptEngine;
pub async fn execute_repl(
engine: &ScriptEngine,
code: &str,
db_name: &str,
variables: &HashMap<String, JsonValue>,
history: &[String],
output_capture: &mut Vec<String>,
) -> Result<(JsonValue, HashMap<String, JsonValue>), DbError> {
engine.stats.active_scripts.fetch_add(1, Ordering::SeqCst);
engine
.stats
.total_scripts_executed
.fetch_add(1, Ordering::SeqCst);
struct ActiveScriptGuard(Arc<crate::scripting::types::ScriptStats>);
impl Drop for ActiveScriptGuard {
fn drop(&mut self) {
self.0.active_scripts.fetch_sub(1, Ordering::SeqCst);
}
}
let _guard = ActiveScriptGuard(engine.stats.clone());
let lua = Lua::new();
let globals = lua.globals();
globals
.set("os", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure os: {}", e)))?;
globals
.set("io", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure io: {}", e)))?;
globals
.set("debug", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure debug: {}", e)))?;
globals
.set("package", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure package: {}", e)))?;
globals
.set("dofile", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure dofile: {}", e)))?;
globals
.set("load", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure load: {}", e)))?;
globals
.set("loadfile", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure loadfile: {}", e)))?;
globals
.set("require", LuaValue::Nil)
.map_err(|e| DbError::InternalError(format!("Failed to secure require: {}", e)))?;
let context = crate::scripting::types::ScriptContext {
method: "REPL".to_string(),
path: "repl".to_string(),
query_params: HashMap::new(),
params: HashMap::new(),
headers: HashMap::new(),
body: None,
is_websocket: false,
user: crate::scripting::auth::ScriptUser::anonymous(),
};
engine.setup_lua_globals(&lua, db_name, &context, None)?;
for (name, value) in variables {
if let JsonValue::Object(ref obj) = value {
if obj.get("_solidb_handle").and_then(|v| v.as_bool()) == Some(true) {
if let Some(coll_name) = obj.get("_name").and_then(|v| v.as_str()) {
let recreate_code = format!("{} = db:collection(\"{}\")", name, coll_name);
let _ = lua.load(&recreate_code).exec();
continue;
}
}
}
let lua_val = json_to_lua(&lua, value).map_err(|e| {
DbError::InternalError(format!("Failed to convert variable '{}': {}", name, e))
})?;
globals.set(name.clone(), lua_val).map_err(|e| {
DbError::InternalError(format!("Failed to inject variable '{}': {}", name, e))
})?;
}
for prev_code in history {
let trimmed = prev_code.trim();
if trimmed.starts_with("function ")
|| trimmed.contains("= function")
|| trimmed.starts_with("local function ")
{
let _ = lua.load(prev_code).exec();
}
}
let output_clone = Arc::new(std::sync::Mutex::new(output_capture.clone()));
let output_ref = output_clone.clone();
let capture_log_fn = lua
.create_function(move |lua, val: mlua::Value| {
let msg = match val {
mlua::Value::Nil => "nil".to_string(),
mlua::Value::Boolean(b) => b.to_string(),
mlua::Value::Integer(i) => i.to_string(),
mlua::Value::Number(n) => n.to_string(),
mlua::Value::String(s) => s
.to_str()
.map(|s| s.to_string())
.unwrap_or_else(|_| "[invalid string]".to_string()),
mlua::Value::Table(t) => {
if let Ok(json) = table_to_json_static(lua, t) {
serde_json::to_string(&json).unwrap_or_else(|_| "[table]".to_string())
} else {
"[table]".to_string()
}
}
_ => "[unsupported type]".to_string(),
};
if let Ok(mut output) = output_ref.lock() {
output.push(msg);
}
Ok(())
})
.map_err(|e| DbError::InternalError(format!("Failed to create capture log fn: {}", e)))?;
let solidb: mlua::Table = globals
.get("solidb")
.map_err(|e| DbError::InternalError(format!("Failed to get solidb table: {}", e)))?;
solidb
.set("log", capture_log_fn)
.map_err(|e| DbError::InternalError(format!("Failed to set capture log: {}", e)))?;
let output_print_ref = output_clone.clone();
let print_fn = lua
.create_function(move |lua, args: mlua::Variadic<mlua::Value>| {
let mut parts = Vec::new();
for val in args {
let part = match val {
mlua::Value::Nil => "nil".to_string(),
mlua::Value::Boolean(b) => b.to_string(),
mlua::Value::Integer(i) => i.to_string(),
mlua::Value::Number(n) => n.to_string(),
mlua::Value::String(s) => s
.to_str()
.map(|s| s.to_string())
.unwrap_or_else(|_| "[invalid string]".to_string()),
mlua::Value::Table(t) => {
if let Ok(json) = table_to_json_static(lua, t) {
serde_json::to_string(&json).unwrap_or_else(|_| "[table]".to_string())
} else {
"[table]".to_string()
}
}
_ => "[unsupported type]".to_string(),
};
parts.push(part);
}
if let Ok(mut output) = output_print_ref.lock() {
output.push(parts.join("\t"));
}
Ok(())
})
.map_err(|e| DbError::InternalError(format!("Failed to create print fn: {}", e)))?;
globals
.set("print", print_fn)
.map_err(|e| DbError::InternalError(format!("Failed to set print: {}", e)))?;
let chunk = lua.load(code);
let result = match chunk.eval_async::<LuaValue>().await {
Ok(result) => {
let json_result = engine.lua_to_json(&lua, result)?;
Ok(json_result)
}
Err(e) => Err(DbError::InternalError(format!("Lua error: {}", e))),
};
if let Ok(captured) = output_clone.lock() {
output_capture.clear();
output_capture.extend(captured.iter().cloned());
}
let mut updated_vars = HashMap::new();
let skip_globals: std::collections::HashSet<&str> = [
"solidb",
"string",
"table",
"math",
"utf8",
"bit32",
"coroutine",
"print",
"type",
"tostring",
"tonumber",
"pairs",
"ipairs",
"next",
"select",
"error",
"pcall",
"xpcall",
"assert",
"rawget",
"rawset",
"rawequal",
"rawlen",
"setmetatable",
"getmetatable",
"collectgarbage",
"_G",
"_VERSION",
"db",
"request",
"response",
"time",
"os",
"io",
"debug",
"package",
"dofile",
"load",
"loadfile",
"require",
]
.iter()
.cloned()
.collect();
if let Ok(pairs) = globals
.pairs::<String, LuaValue>()
.collect::<Result<Vec<_>, _>>()
{
for (name, val) in pairs {
if skip_globals.contains(name.as_str()) || matches!(val, LuaValue::Nil) {
continue;
}
if matches!(val, LuaValue::Function(_)) {
continue;
}
if let LuaValue::Table(ref t) = val {
if t.get::<bool>("_solidb_handle").unwrap_or(false) {
let mut handle_meta = serde_json::Map::new();
handle_meta.insert("_solidb_handle".to_string(), JsonValue::Bool(true));
if let Ok(db_name) = t.get::<String>("_db") {
handle_meta.insert("_db".to_string(), JsonValue::String(db_name));
}
if let Ok(coll_name) = t.get::<String>("_name") {
handle_meta.insert("_name".to_string(), JsonValue::String(coll_name));
}
updated_vars.insert(name, JsonValue::Object(handle_meta));
continue;
}
}
if let Ok(json_val) = engine.lua_to_json(&lua, val) {
updated_vars.insert(name, json_val);
}
}
}
match result {
Ok(json_result) => Ok((json_result, updated_vars)),
Err(e) => Err(e),
}
}
pub(crate) fn table_to_json_static(
lua: &Lua,
table: mlua::Table,
) -> Result<JsonValue, mlua::Error> {
let mut is_array = true;
let mut expected_index = 1i64;
for pair in table.clone().pairs::<LuaValue, LuaValue>() {
let (k, _) = pair?;
match k {
LuaValue::Integer(i) if i == expected_index => {
expected_index += 1;
}
_ => {
is_array = false;
break;
}
}
}
if is_array && expected_index > 1 {
let mut arr = Vec::new();
for i in 1..expected_index {
let val: LuaValue = table.get(i)?;
arr.push(lua_value_to_json_static(lua, val)?);
}
Ok(JsonValue::Array(arr))
} else {
let mut map = serde_json::Map::new();
for pair in table.pairs::<LuaValue, LuaValue>() {
let (k, v) = pair?;
let key_str = match k {
LuaValue::String(s) => s.to_str()?.to_string(),
LuaValue::Integer(i) => i.to_string(),
LuaValue::Number(n) => n.to_string(),
_ => continue,
};
map.insert(key_str, lua_value_to_json_static(lua, v)?);
}
Ok(JsonValue::Object(map))
}
}
pub(crate) fn lua_value_to_json_static(
lua: &Lua,
value: LuaValue,
) -> Result<JsonValue, mlua::Error> {
match value {
LuaValue::Nil => Ok(JsonValue::Null),
LuaValue::Boolean(b) => Ok(JsonValue::Bool(b)),
LuaValue::Integer(i) => Ok(JsonValue::Number(i.into())),
LuaValue::Number(n) => Ok(serde_json::Number::from_f64(n)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null)),
LuaValue::String(s) => Ok(JsonValue::String(s.to_str()?.to_string())),
LuaValue::Table(t) => table_to_json_static(lua, t),
_ => Ok(JsonValue::Null),
}
}