use algocline_core::CustomMetricsHandle;
use mlua::prelude::*;
use mlua::LuaSerdeExt;
use crate::state;
pub(super) fn register_json(lua: &Lua, alc_table: &LuaTable) -> LuaResult<()> {
let encode = lua.create_function(|lua, value: LuaValue| {
let json: serde_json::Value = lua.from_value(value)?;
serde_json::to_string(&json).map_err(LuaError::external)
})?;
let decode = lua.create_function(|lua, s: String| {
let value: serde_json::Value = serde_json::from_str(&s).map_err(LuaError::external)?;
lua.to_value(&value)
})?;
alc_table.set("json_encode", encode)?;
alc_table.set("json_decode", decode)?;
Ok(())
}
pub(super) fn register_log(_lua: &Lua, alc_table: &LuaTable) -> LuaResult<()> {
let log = _lua.create_function(|_, (level, msg): (String, String)| {
match level.as_str() {
"error" => tracing::error!("{}", msg),
"warn" => tracing::warn!("{}", msg),
"info" => tracing::info!("{}", msg),
"debug" => tracing::debug!("{}", msg),
_ => tracing::info!("{}", msg),
}
Ok(())
})?;
alc_table.set("log", log)?;
Ok(())
}
pub(super) fn register_state(lua: &Lua, alc_table: &LuaTable, ns: String) -> LuaResult<()> {
let state_table = lua.create_table()?;
let ns_get = ns.clone();
let get =
lua.create_function(
move |lua, (key, default): (String, Option<LuaValue>)| match state::get(&ns_get, &key) {
Ok(Some(v)) => lua.to_value(&v),
Ok(None) => Ok(default.unwrap_or(LuaValue::Nil)),
Err(e) => Err(LuaError::external(e)),
},
)?;
let ns_set = ns.clone();
let set = lua.create_function(move |lua, (key, value): (String, LuaValue)| {
let json: serde_json::Value = lua.from_value(value)?;
state::set(&ns_set, &key, json).map_err(LuaError::external)
})?;
let ns_keys = ns.clone();
let keys = lua.create_function(move |lua, ()| {
let k = state::keys(&ns_keys).map_err(LuaError::external)?;
lua.to_value(&k)
})?;
let ns_del = ns.clone();
let delete = lua.create_function(move |_, key: String| {
state::delete(&ns_del, &key).map_err(LuaError::external)
})?;
state_table.set("get", get)?;
state_table.set("set", set)?;
state_table.set("keys", keys)?;
state_table.set("delete", delete)?;
alc_table.set("state", state_table)?;
Ok(())
}
pub(super) fn register_stats(
lua: &Lua,
alc_table: &LuaTable,
custom_metrics: CustomMetricsHandle,
) -> LuaResult<()> {
let stats_table = lua.create_table()?;
let cm_record = custom_metrics.clone();
let record = lua.create_function(move |lua, (key, value): (String, LuaValue)| {
let json: serde_json::Value = lua.from_value(value)?;
cm_record.record(key, json);
Ok(())
})?;
let cm_get = custom_metrics;
let get = lua.create_function(move |lua, key: String| match cm_get.get(&key) {
Some(v) => lua.to_value(&v),
None => Ok(LuaValue::Nil),
})?;
stats_table.set("record", record)?;
stats_table.set("get", get)?;
alc_table.set("stats", stats_table)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use algocline_core::ExecutionMetrics;
fn test_config() -> crate::bridge::BridgeConfig {
let metrics = ExecutionMetrics::new();
crate::bridge::BridgeConfig {
llm_tx: None,
ns: "default".into(),
custom_metrics: metrics.custom_metrics_handle(),
budget: metrics.budget_handle(),
progress: metrics.progress_handle(),
lib_paths: vec![],
}
}
fn test_config_with_ns(ns: &str) -> crate::bridge::BridgeConfig {
let metrics = ExecutionMetrics::new();
crate::bridge::BridgeConfig {
llm_tx: None,
ns: ns.into(),
custom_metrics: metrics.custom_metrics_handle(),
budget: metrics.budget_handle(),
progress: metrics.progress_handle(),
lib_paths: vec![],
}
}
#[test]
fn json_roundtrip() {
let lua = Lua::new();
let t = lua.create_table().unwrap();
crate::bridge::register(&lua, &t, test_config()).unwrap();
lua.globals().set("alc", t).unwrap();
let result: String = lua
.load(r#"return alc.json_encode({hello = "world", n = 42})"#)
.eval()
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert_eq!(parsed["hello"], "world");
assert_eq!(parsed["n"], 42);
}
#[test]
fn json_decode_encode() {
let lua = Lua::new();
let t = lua.create_table().unwrap();
crate::bridge::register(&lua, &t, test_config()).unwrap();
lua.globals().set("alc", t).unwrap();
let result: String = lua
.load(
r#"
local val = alc.json_decode('{"a":1,"b":"two"}')
val.c = true
return alc.json_encode(val)
"#,
)
.eval()
.unwrap();
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert_eq!(parsed["a"], 1);
assert_eq!(parsed["b"], "two");
assert_eq!(parsed["c"], true);
}
#[test]
fn state_get_set() {
let ns = "_test_bridge_state";
let _ = crate::state::delete(ns, "x");
let lua = Lua::new();
let t = lua.create_table().unwrap();
crate::bridge::register(&lua, &t, test_config_with_ns(ns)).unwrap();
lua.globals().set("alc", t).unwrap();
lua.load(r#"alc.state.set("x", 99)"#).exec().unwrap();
let result: i64 = lua.load(r#"return alc.state.get("x")"#).eval().unwrap();
assert_eq!(result, 99);
let result: i64 = lua
.load(r#"return alc.state.get("missing", 0)"#)
.eval()
.unwrap();
assert_eq!(result, 0);
let result: LuaValue = lua
.load(r#"return alc.state.get("missing")"#)
.eval()
.unwrap();
assert!(result.is_nil());
let _ = crate::state::delete(ns, "x");
}
#[test]
fn stats_record_get() {
let metrics = ExecutionMetrics::new();
let custom_handle = metrics.custom_metrics_handle();
let lua = Lua::new();
let t = lua.create_table().unwrap();
crate::bridge::register(
&lua,
&t,
crate::bridge::BridgeConfig {
llm_tx: None,
ns: "default".into(),
custom_metrics: custom_handle.clone(),
budget: metrics.budget_handle(),
progress: metrics.progress_handle(),
lib_paths: vec![],
},
)
.unwrap();
lua.globals().set("alc", t).unwrap();
lua.load(r#"alc.stats.record("score", 42)"#).exec().unwrap();
let result: i64 = lua.load(r#"return alc.stats.get("score")"#).eval().unwrap();
assert_eq!(result, 42);
assert_eq!(custom_handle.get("score"), Some(serde_json::json!(42)));
let result: LuaValue = lua
.load(r#"return alc.stats.get("missing")"#)
.eval()
.unwrap();
assert!(result.is_nil());
}
}