1use std::cell::RefCell;
28
29use crate::value::StringKeyedValue;
30
31pub type BuiltinBridgeFn = Box<dyn Fn(&str, Vec<StringKeyedValue>) -> Result<StringKeyedValue, String>>;
43
44thread_local! {
45 static BUILTIN_BRIDGE: RefCell<Option<BuiltinBridgeFn>> = const { RefCell::new(None) };
46}
47
48pub fn set_builtin_bridge(bridge: BuiltinBridgeFn) -> BuiltinBridgeGuard {
54 let prev = BUILTIN_BRIDGE.with(|b| b.borrow_mut().replace(bridge));
55 BuiltinBridgeGuard { _prev: prev }
56}
57
58pub struct BuiltinBridgeGuard {
60 _prev: Option<BuiltinBridgeFn>,
61}
62
63impl Drop for BuiltinBridgeGuard {
64 fn drop(&mut self) {
65 let prev = self._prev.take();
66 BUILTIN_BRIDGE.with(|b| *b.borrow_mut() = prev);
67 }
68}
69
70pub fn call_builtin_bridge(
77 name: &str,
78 args: Vec<StringKeyedValue>,
79) -> Result<Option<StringKeyedValue>, String> {
80 BUILTIN_BRIDGE.with(|b| {
81 let borrow = b.borrow();
82 if let Some(ref bridge) = *borrow {
83 bridge(name, args).map(Some)
84 } else {
85 Ok(None) }
87 })
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn no_bridge_returns_none() {
96 let result = call_builtin_bridge("getEnv", vec![StringKeyedValue::String("HOME".into())]);
97 assert!(matches!(result, Ok(None)));
98 }
99
100 #[test]
101 fn bridge_handles_call() {
102 let _guard = set_builtin_bridge(Box::new(|name, args| {
103 assert_eq!(name, "getEnv");
104 match &args[0] {
105 StringKeyedValue::String(s) => {
106 Ok(StringKeyedValue::String(format!("mocked:{s}")))
107 }
108 _ => Err("expected string".into()),
109 }
110 }));
111
112 let result = call_builtin_bridge(
113 "getEnv",
114 vec![StringKeyedValue::String("HOME".into())],
115 );
116 assert_eq!(
117 result.unwrap().unwrap(),
118 StringKeyedValue::String("mocked:HOME".into())
119 );
120 }
121
122 #[test]
123 fn bridge_error_propagates() {
124 let _guard = set_builtin_bridge(Box::new(|_, _| {
125 Err("bridge error".into())
126 }));
127
128 let result = call_builtin_bridge("anything", vec![]);
129 assert_eq!(result.unwrap_err(), "bridge error");
130 }
131
132 #[test]
133 fn guard_clears_bridge_on_drop() {
134 {
135 let _guard = set_builtin_bridge(Box::new(|_, _| {
136 Ok(StringKeyedValue::Null)
137 }));
138 assert!(matches!(
139 call_builtin_bridge("x", vec![]),
140 Ok(Some(StringKeyedValue::Null))
141 ));
142 }
143 assert!(matches!(call_builtin_bridge("x", vec![]), Ok(None)));
145 }
146
147 #[test]
150 fn set_builtin_bridge_installs_callback() {
151 let _guard = set_builtin_bridge(Box::new(|name, _| {
152 Ok(StringKeyedValue::String(format!("handled:{name}")))
153 }));
154 let result = call_builtin_bridge("myBuiltin", vec![]);
155 assert_eq!(
156 result.unwrap().unwrap(),
157 StringKeyedValue::String("handled:myBuiltin".into())
158 );
159 }
160
161 #[test]
164 fn raii_guard_restores_previous_bridge() {
165 let _outer = set_builtin_bridge(Box::new(|_, _| {
167 Ok(StringKeyedValue::String("outer".into()))
168 }));
169 {
170 let _inner = set_builtin_bridge(Box::new(|_, _| {
172 Ok(StringKeyedValue::String("inner".into()))
173 }));
174 let result = call_builtin_bridge("x", vec![]);
175 assert_eq!(
176 result.unwrap().unwrap(),
177 StringKeyedValue::String("inner".into())
178 );
179 }
180 let result = call_builtin_bridge("x", vec![]);
182 assert_eq!(
183 result.unwrap().unwrap(),
184 StringKeyedValue::String("outer".into())
185 );
186 }
187
188 #[test]
191 fn call_builtin_bridge_returns_none_when_no_bridge() {
192 {
194 let _guard = set_builtin_bridge(Box::new(|_, _| Ok(StringKeyedValue::Null)));
195 }
196 let result = call_builtin_bridge("nonexistent", vec![]);
197 assert!(matches!(result, Ok(None)));
198 }
199
200 #[test]
203 fn call_builtin_bridge_returns_some_when_bridge_set() {
204 let _guard = set_builtin_bridge(Box::new(|_, _| {
205 Ok(StringKeyedValue::Int(42))
206 }));
207 let result = call_builtin_bridge("anything", vec![]);
208 assert!(result.is_ok());
209 assert!(result.unwrap().is_some());
210 }
211
212 #[test]
215 fn bridge_with_string_argument_and_return() {
216 let _guard = set_builtin_bridge(Box::new(|name, args| {
217 assert_eq!(name, "echo");
218 match &args[0] {
219 StringKeyedValue::String(s) => {
220 Ok(StringKeyedValue::String(format!("echo:{s}")))
221 }
222 _ => Err("expected string arg".into()),
223 }
224 }));
225 let result = call_builtin_bridge(
226 "echo",
227 vec![StringKeyedValue::String("hello".into())],
228 );
229 assert_eq!(
230 result.unwrap().unwrap(),
231 StringKeyedValue::String("echo:hello".into())
232 );
233 }
234
235 #[test]
238 fn bridge_with_attrset_argument() {
239 let _guard = set_builtin_bridge(Box::new(|name, args| {
240 assert_eq!(name, "inspect");
241 match &args[0] {
242 StringKeyedValue::Attrs(map) => {
243 let keys: Vec<&String> = map.keys().collect();
244 Ok(StringKeyedValue::Int(keys.len() as i64))
245 }
246 _ => Err("expected attrset".into()),
247 }
248 }));
249
250 let mut attrs = std::collections::BTreeMap::new();
251 attrs.insert("a".to_string(), StringKeyedValue::Int(1));
252 attrs.insert("b".to_string(), StringKeyedValue::Int(2));
253 attrs.insert("c".to_string(), StringKeyedValue::Int(3));
254
255 let result = call_builtin_bridge(
256 "inspect",
257 vec![StringKeyedValue::Attrs(attrs)],
258 );
259 assert_eq!(result.unwrap().unwrap(), StringKeyedValue::Int(3));
260 }
261}