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
90pub type PathMaterializerFn = Box<dyn Fn(&str) -> String>;
115
116thread_local! {
117 static PATH_MATERIALIZER: RefCell<Option<PathMaterializerFn>> = const { RefCell::new(None) };
118}
119
120pub fn set_path_materializer(materializer: PathMaterializerFn) -> PathMaterializerGuard {
124 let prev = PATH_MATERIALIZER.with(|m| m.borrow_mut().replace(materializer));
125 PathMaterializerGuard { _prev: prev }
126}
127
128pub struct PathMaterializerGuard {
130 _prev: Option<PathMaterializerFn>,
131}
132
133impl Drop for PathMaterializerGuard {
134 fn drop(&mut self) {
135 let prev = self._prev.take();
136 PATH_MATERIALIZER.with(|m| *m.borrow_mut() = prev);
137 }
138}
139
140#[must_use]
147pub fn materialize(path: &str) -> String {
148 PATH_MATERIALIZER.with(|m| {
149 let borrow = m.borrow();
150 match *borrow {
151 Some(ref f) => f(path),
152 None => path.to_string(),
153 }
154 })
155}
156
157#[must_use]
159pub fn materialize_path(path: &std::path::Path) -> std::path::PathBuf {
160 std::path::PathBuf::from(materialize(&path.to_string_lossy()))
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn no_materializer_is_the_identity() {
169 assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/nix/store/abc-source/flake.nix");
171 assert_eq!(materialize("relative/path.nix"), "relative/path.nix");
172 }
173
174 #[test]
175 fn materializer_redirects_and_guard_restores() {
176 {
177 let _guard = set_path_materializer(Box::new(|p: &str| {
178 p.replace("/nix/store/abc-source", "/cache/abc")
179 }));
180 assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/cache/abc/flake.nix");
181 assert_eq!(materialize("/etc/nix/nix.conf"), "/etc/nix/nix.conf");
183 }
184 assert_eq!(materialize("/nix/store/abc-source/flake.nix"), "/nix/store/abc-source/flake.nix");
186 }
187
188 #[test]
189 fn materializer_guard_restores_previous() {
190 let _outer = set_path_materializer(Box::new(|_: &str| "/outer".to_string()));
191 {
192 let _inner = set_path_materializer(Box::new(|_: &str| "/inner".to_string()));
193 assert_eq!(materialize("/x"), "/inner");
194 }
195 assert_eq!(materialize("/x"), "/outer");
196 }
197
198 #[test]
199 fn materialize_path_roundtrips_through_pathbuf() {
200 let _guard = set_path_materializer(Box::new(|p: &str| p.replace("/store", "/real")));
201 assert_eq!(
202 materialize_path(std::path::Path::new("/store/f.nix")),
203 std::path::PathBuf::from("/real/f.nix")
204 );
205 }
206
207 #[test]
208 fn no_bridge_returns_none() {
209 let result = call_builtin_bridge("getEnv", vec![StringKeyedValue::String("HOME".into())]);
210 assert!(matches!(result, Ok(None)));
211 }
212
213 #[test]
214 fn bridge_handles_call() {
215 let _guard = set_builtin_bridge(Box::new(|name, args| {
216 assert_eq!(name, "getEnv");
217 match &args[0] {
218 StringKeyedValue::String(s) => {
219 Ok(StringKeyedValue::String(format!("mocked:{s}")))
220 }
221 _ => Err("expected string".into()),
222 }
223 }));
224
225 let result = call_builtin_bridge(
226 "getEnv",
227 vec![StringKeyedValue::String("HOME".into())],
228 );
229 assert_eq!(
230 result.unwrap().unwrap(),
231 StringKeyedValue::String("mocked:HOME".into())
232 );
233 }
234
235 #[test]
236 fn bridge_error_propagates() {
237 let _guard = set_builtin_bridge(Box::new(|_, _| {
238 Err("bridge error".into())
239 }));
240
241 let result = call_builtin_bridge("anything", vec![]);
242 assert_eq!(result.unwrap_err(), "bridge error");
243 }
244
245 #[test]
246 fn guard_clears_bridge_on_drop() {
247 {
248 let _guard = set_builtin_bridge(Box::new(|_, _| {
249 Ok(StringKeyedValue::Null)
250 }));
251 assert!(matches!(
252 call_builtin_bridge("x", vec![]),
253 Ok(Some(StringKeyedValue::Null))
254 ));
255 }
256 assert!(matches!(call_builtin_bridge("x", vec![]), Ok(None)));
258 }
259
260 #[test]
263 fn set_builtin_bridge_installs_callback() {
264 let _guard = set_builtin_bridge(Box::new(|name, _| {
265 Ok(StringKeyedValue::String(format!("handled:{name}")))
266 }));
267 let result = call_builtin_bridge("myBuiltin", vec![]);
268 assert_eq!(
269 result.unwrap().unwrap(),
270 StringKeyedValue::String("handled:myBuiltin".into())
271 );
272 }
273
274 #[test]
277 fn raii_guard_restores_previous_bridge() {
278 let _outer = set_builtin_bridge(Box::new(|_, _| {
280 Ok(StringKeyedValue::String("outer".into()))
281 }));
282 {
283 let _inner = set_builtin_bridge(Box::new(|_, _| {
285 Ok(StringKeyedValue::String("inner".into()))
286 }));
287 let result = call_builtin_bridge("x", vec![]);
288 assert_eq!(
289 result.unwrap().unwrap(),
290 StringKeyedValue::String("inner".into())
291 );
292 }
293 let result = call_builtin_bridge("x", vec![]);
295 assert_eq!(
296 result.unwrap().unwrap(),
297 StringKeyedValue::String("outer".into())
298 );
299 }
300
301 #[test]
304 fn call_builtin_bridge_returns_none_when_no_bridge() {
305 {
307 let _guard = set_builtin_bridge(Box::new(|_, _| Ok(StringKeyedValue::Null)));
308 }
309 let result = call_builtin_bridge("nonexistent", vec![]);
310 assert!(matches!(result, Ok(None)));
311 }
312
313 #[test]
316 fn call_builtin_bridge_returns_some_when_bridge_set() {
317 let _guard = set_builtin_bridge(Box::new(|_, _| {
318 Ok(StringKeyedValue::Int(42))
319 }));
320 let result = call_builtin_bridge("anything", vec![]);
321 assert!(result.is_ok());
322 assert!(result.unwrap().is_some());
323 }
324
325 #[test]
328 fn bridge_with_string_argument_and_return() {
329 let _guard = set_builtin_bridge(Box::new(|name, args| {
330 assert_eq!(name, "echo");
331 match &args[0] {
332 StringKeyedValue::String(s) => {
333 Ok(StringKeyedValue::String(format!("echo:{s}")))
334 }
335 _ => Err("expected string arg".into()),
336 }
337 }));
338 let result = call_builtin_bridge(
339 "echo",
340 vec![StringKeyedValue::String("hello".into())],
341 );
342 assert_eq!(
343 result.unwrap().unwrap(),
344 StringKeyedValue::String("echo:hello".into())
345 );
346 }
347
348 #[test]
351 fn bridge_with_attrset_argument() {
352 let _guard = set_builtin_bridge(Box::new(|name, args| {
353 assert_eq!(name, "inspect");
354 match &args[0] {
355 StringKeyedValue::Attrs(map) => {
356 let keys: Vec<&String> = map.keys().collect();
357 Ok(StringKeyedValue::Int(keys.len() as i64))
358 }
359 _ => Err("expected attrset".into()),
360 }
361 }));
362
363 let mut attrs = std::collections::BTreeMap::new();
364 attrs.insert("a".to_string(), StringKeyedValue::Int(1));
365 attrs.insert("b".to_string(), StringKeyedValue::Int(2));
366 attrs.insert("c".to_string(), StringKeyedValue::Int(3));
367
368 let result = call_builtin_bridge(
369 "inspect",
370 vec![StringKeyedValue::Attrs(attrs)],
371 );
372 assert_eq!(result.unwrap().unwrap(), StringKeyedValue::Int(3));
373 }
374}