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) }
})
}
pub type PathMaterializerFn = Box<dyn Fn(&str) -> String>;
thread_local! {
static PATH_MATERIALIZER: RefCell<Option<PathMaterializerFn>> = const { RefCell::new(None) };
}
pub fn set_path_materializer(materializer: PathMaterializerFn) -> PathMaterializerGuard {
let prev = PATH_MATERIALIZER.with(|m| m.borrow_mut().replace(materializer));
PathMaterializerGuard { _prev: prev }
}
pub struct PathMaterializerGuard {
_prev: Option<PathMaterializerFn>,
}
impl Drop for PathMaterializerGuard {
fn drop(&mut self) {
let prev = self._prev.take();
PATH_MATERIALIZER.with(|m| *m.borrow_mut() = prev);
}
}
#[must_use]
pub fn materialize(path: &str) -> String {
PATH_MATERIALIZER.with(|m| {
let borrow = m.borrow();
match *borrow {
Some(ref f) => f(path),
None => path.to_string(),
}
})
}
#[must_use]
pub fn materialize_path(path: &std::path::Path) -> std::path::PathBuf {
std::path::PathBuf::from(materialize(&path.to_string_lossy()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_materializer_is_the_identity() {
assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/nix/store/abc-source/flake.nix");
assert_eq!(materialize("relative/path.nix"), "relative/path.nix");
}
#[test]
fn materializer_redirects_and_guard_restores() {
{
let _guard = set_path_materializer(Box::new(|p: &str| {
p.replace("/nix/store/abc-source", "/cache/abc")
}));
assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/cache/abc/flake.nix");
assert_eq!(materialize("/etc/nix/nix.conf"), "/etc/nix/nix.conf");
}
assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/nix/store/abc-source/flake.nix");
}
#[test]
fn materializer_guard_restores_previous() {
let _outer = set_path_materializer(Box::new(|_: &str| "/outer".to_string()));
{
let _inner = set_path_materializer(Box::new(|_: &str| "/inner".to_string()));
assert_eq!(materialize("/x"), "/inner");
}
assert_eq!(materialize("/x"), "/outer");
}
#[test]
fn materialize_path_roundtrips_through_pathbuf() {
let _guard = set_path_materializer(Box::new(|p: &str| p.replace("/store", "/real")));
assert_eq!(
materialize_path(std::path::Path::new("/store/f.nix")),
std::path::PathBuf::from("/real/f.nix")
);
}
#[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));
}
}