use std::cell::RefCell;
use crate::value::StringKeyedValue;
pub type BuiltinBridgeFn = Box<dyn Fn(&str, Vec<StringKeyedValue>) -> Result<StringKeyedValue, String>>;
thread_local! {
static BUILTIN_BRIDGE: RefCell<Option<BuiltinBridgeFn>> = const { RefCell::new(None) };
}
pub fn set_builtin_bridge(bridge: BuiltinBridgeFn) -> BuiltinBridgeGuard {
let prev = BUILTIN_BRIDGE.with(|b| b.borrow_mut().replace(bridge));
BuiltinBridgeGuard { _prev: prev }
}
pub struct BuiltinBridgeGuard {
_prev: Option<BuiltinBridgeFn>,
}
impl Drop for BuiltinBridgeGuard {
fn drop(&mut self) {
let prev = self._prev.take();
BUILTIN_BRIDGE.with(|b| *b.borrow_mut() = prev);
}
}
pub fn call_builtin_bridge(
name: &str,
args: Vec<StringKeyedValue>,
) -> Result<Option<StringKeyedValue>, String> {
BUILTIN_BRIDGE.with(|b| {
let borrow = b.borrow();
if let Some(ref bridge) = *borrow {
bridge(name, args).map(Some)
} else {
Ok(None) }
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_bridge_returns_none() {
let result = call_builtin_bridge("getEnv", vec![StringKeyedValue::String("HOME".into())]);
assert!(matches!(result, Ok(None)));
}
#[test]
fn bridge_handles_call() {
let _guard = set_builtin_bridge(Box::new(|name, args| {
assert_eq!(name, "getEnv");
match &args[0] {
StringKeyedValue::String(s) => {
Ok(StringKeyedValue::String(format!("mocked:{s}")))
}
_ => Err("expected string".into()),
}
}));
let result = call_builtin_bridge(
"getEnv",
vec![StringKeyedValue::String("HOME".into())],
);
assert_eq!(
result.unwrap().unwrap(),
StringKeyedValue::String("mocked:HOME".into())
);
}
#[test]
fn bridge_error_propagates() {
let _guard = set_builtin_bridge(Box::new(|_, _| {
Err("bridge error".into())
}));
let result = call_builtin_bridge("anything", vec![]);
assert_eq!(result.unwrap_err(), "bridge error");
}
#[test]
fn guard_clears_bridge_on_drop() {
{
let _guard = set_builtin_bridge(Box::new(|_, _| {
Ok(StringKeyedValue::Null)
}));
assert!(matches!(
call_builtin_bridge("x", vec![]),
Ok(Some(StringKeyedValue::Null))
));
}
assert!(matches!(call_builtin_bridge("x", vec![]), Ok(None)));
}
#[test]
fn set_builtin_bridge_installs_callback() {
let _guard = set_builtin_bridge(Box::new(|name, _| {
Ok(StringKeyedValue::String(format!("handled:{name}")))
}));
let result = call_builtin_bridge("myBuiltin", vec![]);
assert_eq!(
result.unwrap().unwrap(),
StringKeyedValue::String("handled:myBuiltin".into())
);
}
#[test]
fn raii_guard_restores_previous_bridge() {
let _outer = set_builtin_bridge(Box::new(|_, _| {
Ok(StringKeyedValue::String("outer".into()))
}));
{
let _inner = set_builtin_bridge(Box::new(|_, _| {
Ok(StringKeyedValue::String("inner".into()))
}));
let result = call_builtin_bridge("x", vec![]);
assert_eq!(
result.unwrap().unwrap(),
StringKeyedValue::String("inner".into())
);
}
let result = call_builtin_bridge("x", vec![]);
assert_eq!(
result.unwrap().unwrap(),
StringKeyedValue::String("outer".into())
);
}
#[test]
fn call_builtin_bridge_returns_none_when_no_bridge() {
{
let _guard = set_builtin_bridge(Box::new(|_, _| Ok(StringKeyedValue::Null)));
}
let result = call_builtin_bridge("nonexistent", vec![]);
assert!(matches!(result, Ok(None)));
}
#[test]
fn call_builtin_bridge_returns_some_when_bridge_set() {
let _guard = set_builtin_bridge(Box::new(|_, _| {
Ok(StringKeyedValue::Int(42))
}));
let result = call_builtin_bridge("anything", vec![]);
assert!(result.is_ok());
assert!(result.unwrap().is_some());
}
#[test]
fn bridge_with_string_argument_and_return() {
let _guard = set_builtin_bridge(Box::new(|name, args| {
assert_eq!(name, "echo");
match &args[0] {
StringKeyedValue::String(s) => {
Ok(StringKeyedValue::String(format!("echo:{s}")))
}
_ => Err("expected string arg".into()),
}
}));
let result = call_builtin_bridge(
"echo",
vec![StringKeyedValue::String("hello".into())],
);
assert_eq!(
result.unwrap().unwrap(),
StringKeyedValue::String("echo:hello".into())
);
}
#[test]
fn bridge_with_attrset_argument() {
let _guard = set_builtin_bridge(Box::new(|name, args| {
assert_eq!(name, "inspect");
match &args[0] {
StringKeyedValue::Attrs(map) => {
let keys: Vec<&String> = map.keys().collect();
Ok(StringKeyedValue::Int(keys.len() as i64))
}
_ => Err("expected attrset".into()),
}
}));
let mut attrs = std::collections::BTreeMap::new();
attrs.insert("a".to_string(), StringKeyedValue::Int(1));
attrs.insert("b".to_string(), StringKeyedValue::Int(2));
attrs.insert("c".to_string(), StringKeyedValue::Int(3));
let result = call_builtin_bridge(
"inspect",
vec![StringKeyedValue::Attrs(attrs)],
);
assert_eq!(result.unwrap().unwrap(), StringKeyedValue::Int(3));
}
}