1use std::{
49 collections::HashSet,
50 ffi::CString,
51 marker::PhantomData,
52 os::raw::c_void,
53 panic::{self, AssertUnwindSafe},
54 sync::{Arc, Mutex, OnceLock},
55};
56
57use crate::{Context, Error, Result, ValueType, check_err, sys};
58
59type PrimOpFn =
60 dyn Fn(&[PrimOpArg<'_>], &mut PrimOpRet<'_>) -> Result<()> + Send + Sync;
61
62struct ClosureData {
67 arity: usize,
68 f: Box<PrimOpFn>,
69}
70
71unsafe extern "C" fn trampoline(
80 user_data: *mut c_void,
81 context: *mut sys::nix_c_context,
82 state: *mut sys::EvalState,
83 args: *mut *mut sys::nix_value,
84 ret: *mut sys::nix_value,
85) {
86 let data = unsafe { &*(user_data as *const ClosureData) };
87
88 let arg_wrappers: Vec<PrimOpArg<'_>> = if data.arity == 0 {
95 Vec::new()
96 } else {
97 let arg_slice = unsafe { std::slice::from_raw_parts(args, data.arity) };
98 arg_slice
99 .iter()
100 .map(|&p| {
101 PrimOpArg {
102 inner: p,
103 ctx: context,
104 state,
105 _phantom: PhantomData,
106 }
107 })
108 .collect()
109 };
110
111 let mut ret_wrapper = PrimOpRet {
112 inner: ret,
113 ctx: context,
114 state,
115 written: false,
116 _phantom: PhantomData,
117 };
118
119 let result = panic::catch_unwind(AssertUnwindSafe(|| {
120 (data.f)(&arg_wrappers, &mut ret_wrapper)
121 }));
122
123 let err_msg = match result {
124 Ok(Ok(())) => {
125 if ret_wrapper.written {
126 return;
127 }
128 "primop returned Ok(()) without calling a set_* method on the return slot"
129 .to_string()
130 },
131 Ok(Err(e)) => format!("primop error: {e}"),
132 Err(payload) => {
133 let detail = if let Some(s) = payload.downcast_ref::<&'static str>() {
137 (*s).to_owned()
138 } else if let Some(s) = payload.downcast_ref::<String>() {
139 s.clone()
140 } else {
141 "unknown panic payload".to_owned()
142 };
143 format!("primop panicked: {detail}")
144 },
145 };
146
147 let msg_c = CString::new(err_msg)
148 .unwrap_or_else(|_| CString::new("primop error").unwrap());
149 unsafe {
150 sys::nix_set_err_msg(
151 context,
152 sys::nix_err_NIX_ERR_NIX_ERROR,
153 msg_c.as_ptr(),
154 );
155 }
156}
157
158unsafe extern "C" fn drop_closure_finalizer(
163 _obj: *mut c_void,
164 cd: *mut c_void,
165) {
166 let _ = unsafe { Box::from_raw(cd as *mut ClosureData) };
168}
169
170pub use crate::value_ops::NixValueOps;
174use crate::value_ops::NixValueRaw;
175
176pub struct PrimOpArg<'a> {
182 inner: *mut sys::nix_value,
183 ctx: *mut sys::nix_c_context,
184 state: *mut sys::EvalState,
185 _phantom: PhantomData<&'a ()>,
186}
187
188impl NixValueRaw for PrimOpArg<'_> {
196 fn raw_ctx(&self) -> *mut sys::nix_c_context {
197 self.ctx
198 }
199
200 fn raw_state(&self) -> *mut sys::EvalState {
201 self.state
202 }
203
204 fn raw_inner(&self) -> *mut sys::nix_value {
205 self.inner
206 }
207}
208
209impl PrimOpArg<'_> {
210 pub fn as_attrs(&self) -> Result<ArgAttrs<'_>> {
227 self.force()?;
228 if self.value_type() != ValueType::Attrs {
229 return Err(Error::InvalidType {
230 expected: "attrs",
231 actual: self.value_type().to_string(),
232 });
233 }
234 Ok(ArgAttrs {
235 inner: self.inner,
236 ctx: self.ctx,
237 state: self.state,
238 _phantom: PhantomData,
239 })
240 }
241
242 pub fn as_list(&self) -> Result<ArgList<'_>> {
255 self.force()?;
256 if self.value_type() != ValueType::List {
257 return Err(Error::InvalidType {
258 expected: "list",
259 actual: self.value_type().to_string(),
260 });
261 }
262 Ok(ArgList {
263 inner: self.inner,
264 ctx: self.ctx,
265 state: self.state,
266 _phantom: PhantomData,
267 })
268 }
269}
270
271pub struct PrimOpRet<'a> {
278 inner: *mut sys::nix_value,
279 ctx: *mut sys::nix_c_context,
280 state: *mut sys::EvalState,
281 written: bool,
284 _phantom: PhantomData<&'a mut ()>,
285}
286
287impl<'a> PrimOpRet<'a> {
288 fn mark_written(&mut self) {
292 self.written = true;
293 }
294
295 pub fn set_int(&mut self, i: i64) -> Result<()> {
301 unsafe { check_err(self.ctx, sys::nix_init_int(self.ctx, self.inner, i)) }?;
302 self.mark_written();
303 Ok(())
304 }
305
306 pub fn set_float(&mut self, f: f64) -> Result<()> {
312 unsafe {
313 check_err(self.ctx, sys::nix_init_float(self.ctx, self.inner, f))
314 }?;
315 self.mark_written();
316 Ok(())
317 }
318
319 pub fn set_bool(&mut self, b: bool) -> Result<()> {
325 unsafe {
326 check_err(self.ctx, sys::nix_init_bool(self.ctx, self.inner, b))
327 }?;
328 self.mark_written();
329 Ok(())
330 }
331
332 pub fn set_null(&mut self) -> Result<()> {
338 unsafe { check_err(self.ctx, sys::nix_init_null(self.ctx, self.inner)) }?;
339 self.mark_written();
340 Ok(())
341 }
342
343 pub fn set_string(&mut self, s: &str) -> Result<()> {
350 let s_c = CString::new(s)?;
351 unsafe {
352 check_err(
353 self.ctx,
354 sys::nix_init_string(self.ctx, self.inner, s_c.as_ptr()),
355 )
356 }?;
357 self.mark_written();
358 Ok(())
359 }
360
361 pub fn set_path(&mut self, p: &str) -> Result<()> {
380 let p_c = CString::new(p)?;
381 unsafe {
382 check_err(
383 self.ctx,
384 sys::nix_init_path_string(
385 self.ctx,
386 self.state,
387 self.inner,
388 p_c.as_ptr(),
389 ),
390 )
391 }?;
392 self.mark_written();
393 Ok(())
394 }
395
396 #[cfg(all(feature = "shim", feature = "store"))]
410 pub fn set_store_path_typed(
411 &mut self,
412 store: &crate::store::Store,
413 path: &crate::store::StorePath,
414 ) -> Result<()> {
415 let rendered = store.print_path(path)?;
416 self.set_store_path(&rendered)
417 }
418
419 #[cfg(feature = "shim")]
437 pub fn set_store_path(&mut self, p: &str) -> Result<()> {
438 let p_c = CString::new(p)?;
439 unsafe {
440 check_err(
441 self.ctx,
442 sys::nix_eval_state_allow_path(self.ctx, self.state, p_c.as_ptr()),
443 )?;
444 check_err(
445 self.ctx,
446 sys::nix_init_path_string(
447 self.ctx,
448 self.state,
449 self.inner,
450 p_c.as_ptr(),
451 ),
452 )
453 }?;
454 self.mark_written();
455 Ok(())
456 }
457
458 pub unsafe fn copy_from_raw(
472 &mut self,
473 src: *mut sys::nix_value,
474 ) -> Result<()> {
475 unsafe {
476 check_err(self.ctx, sys::nix_copy_value(self.ctx, self.inner, src))
477 }?;
478 self.mark_written();
479 Ok(())
480 }
481
482 pub fn set_attrs(
492 &mut self,
493 pairs: &[(&str, &PrimOpValue<'_>)],
494 ) -> Result<()> {
495 let builder = unsafe {
496 sys::nix_make_bindings_builder(self.ctx, self.state, pairs.len().max(1))
497 };
498 if builder.is_null() {
499 return Err(Error::NullPointer);
500 }
501
502 struct BuilderGuard(*mut sys::BindingsBuilder);
503 impl Drop for BuilderGuard {
504 fn drop(&mut self) {
505 unsafe { sys::nix_bindings_builder_free(self.0) };
506 }
507 }
508 let _guard = BuilderGuard(builder);
509
510 for (key, value) in pairs {
511 let key_c = CString::new(*key)?;
512 unsafe {
514 check_err(
515 self.ctx,
516 sys::nix_bindings_builder_insert(
517 self.ctx,
518 builder,
519 key_c.as_ptr(),
520 value.inner,
521 ),
522 )?;
523 }
524 }
525
526 unsafe {
528 check_err(self.ctx, sys::nix_make_attrs(self.ctx, self.inner, builder))
529 }?;
530 self.mark_written();
531 Ok(())
532 }
533
534 pub fn set_list(&mut self, items: &[&PrimOpValue<'_>]) -> Result<()> {
544 let builder = unsafe {
545 sys::nix_make_list_builder(self.ctx, self.state, items.len().max(1))
546 };
547 if builder.is_null() {
548 return Err(Error::NullPointer);
549 }
550
551 struct ListGuard(*mut sys::ListBuilder);
552 impl Drop for ListGuard {
553 fn drop(&mut self) {
554 unsafe { sys::nix_list_builder_free(self.0) };
555 }
556 }
557 let _guard = ListGuard(builder);
558
559 for (i, value) in items.iter().enumerate() {
560 unsafe {
561 check_err(
562 self.ctx,
563 sys::nix_list_builder_insert(
564 self.ctx,
565 builder,
566 i as std::os::raw::c_uint,
567 value.inner,
568 ),
569 )?;
570 }
571 }
572
573 unsafe {
574 check_err(self.ctx, sys::nix_make_list(self.ctx, builder, self.inner))
575 }?;
576 self.mark_written();
577 Ok(())
578 }
579
580 pub fn make_int(&self, i: i64) -> Result<PrimOpValue<'a>> {
586 let v = PrimOpValue::alloc(self.ctx, self.state)?;
587 unsafe {
588 check_err(self.ctx, sys::nix_init_int(self.ctx, v.inner, i))?;
589 }
590 Ok(v)
591 }
592
593 pub fn make_float(&self, f: f64) -> Result<PrimOpValue<'a>> {
599 let v = PrimOpValue::alloc(self.ctx, self.state)?;
600 unsafe {
601 check_err(self.ctx, sys::nix_init_float(self.ctx, v.inner, f))?;
602 }
603 Ok(v)
604 }
605
606 pub fn make_bool(&self, b: bool) -> Result<PrimOpValue<'a>> {
612 let v = PrimOpValue::alloc(self.ctx, self.state)?;
613 unsafe {
614 check_err(self.ctx, sys::nix_init_bool(self.ctx, v.inner, b))?;
615 }
616 Ok(v)
617 }
618
619 pub fn make_null(&self) -> Result<PrimOpValue<'a>> {
625 let v = PrimOpValue::alloc(self.ctx, self.state)?;
626 unsafe {
627 check_err(self.ctx, sys::nix_init_null(self.ctx, v.inner))?;
628 }
629 Ok(v)
630 }
631
632 pub fn make_string(&self, s: &str) -> Result<PrimOpValue<'a>> {
639 let v = PrimOpValue::alloc(self.ctx, self.state)?;
640 let s_c = CString::new(s)?;
641 unsafe {
642 check_err(
643 self.ctx,
644 sys::nix_init_string(self.ctx, v.inner, s_c.as_ptr()),
645 )?;
646 }
647 Ok(v)
648 }
649
650 pub fn make_path(&self, p: &str) -> Result<PrimOpValue<'a>> {
666 let v = PrimOpValue::alloc(self.ctx, self.state)?;
667 let p_c = CString::new(p)?;
668 unsafe {
669 check_err(
670 self.ctx,
671 sys::nix_init_path_string(self.ctx, self.state, v.inner, p_c.as_ptr()),
672 )?;
673 }
674 Ok(v)
675 }
676
677 #[cfg(all(feature = "shim", feature = "store"))]
689 pub fn make_store_path_typed(
690 &self,
691 store: &crate::store::Store,
692 path: &crate::store::StorePath,
693 ) -> Result<PrimOpValue<'a>> {
694 let rendered = store.print_path(path)?;
695 self.make_store_path(&rendered)
696 }
697
698 #[cfg(feature = "shim")]
714 pub fn make_store_path(&self, p: &str) -> Result<PrimOpValue<'a>> {
715 let v = PrimOpValue::alloc(self.ctx, self.state)?;
716 let p_c = CString::new(p)?;
717 unsafe {
718 check_err(
719 self.ctx,
720 sys::nix_eval_state_allow_path(self.ctx, self.state, p_c.as_ptr()),
721 )?;
722 check_err(
723 self.ctx,
724 sys::nix_init_path_string(self.ctx, self.state, v.inner, p_c.as_ptr()),
725 )?;
726 }
727 Ok(v)
728 }
729}
730
731pub struct PrimOpValue<'a> {
741 inner: *mut sys::nix_value,
742 ctx: *mut sys::nix_c_context,
743 state: *mut sys::EvalState,
744 _phantom: PhantomData<&'a ()>,
745}
746
747impl NixValueRaw for PrimOpValue<'_> {
754 fn raw_ctx(&self) -> *mut sys::nix_c_context {
755 self.ctx
756 }
757
758 fn raw_state(&self) -> *mut sys::EvalState {
759 self.state
760 }
761
762 fn raw_inner(&self) -> *mut sys::nix_value {
763 self.inner
764 }
765}
766
767impl<'a> PrimOpValue<'a> {
768 fn alloc(
769 ctx: *mut sys::nix_c_context,
770 state: *mut sys::EvalState,
771 ) -> Result<Self> {
772 let inner = unsafe { sys::nix_alloc_value(ctx, state) };
773 if inner.is_null() {
774 return Err(Error::NullPointer);
775 }
776 Ok(PrimOpValue {
777 inner,
778 ctx,
779 state,
780 _phantom: PhantomData,
781 })
782 }
783
784 pub fn as_attrs(&self) -> Result<ArgAttrs<'_>> {
799 self.force()?;
800 if self.value_type() != ValueType::Attrs {
801 return Err(Error::InvalidType {
802 expected: "attrs",
803 actual: self.value_type().to_string(),
804 });
805 }
806 Ok(ArgAttrs {
807 inner: self.inner,
808 ctx: self.ctx,
809 state: self.state,
810 _phantom: PhantomData,
811 })
812 }
813
814 pub fn as_list(&self) -> Result<ArgList<'_>> {
825 self.force()?;
826 if self.value_type() != ValueType::List {
827 return Err(Error::InvalidType {
828 expected: "list",
829 actual: self.value_type().to_string(),
830 });
831 }
832 Ok(ArgList {
833 inner: self.inner,
834 ctx: self.ctx,
835 state: self.state,
836 _phantom: PhantomData,
837 })
838 }
839}
840
841impl Drop for PrimOpValue<'_> {
842 fn drop(&mut self) {
843 unsafe {
845 sys::nix_value_decref(self.ctx, self.inner);
846 }
847 }
848}
849pub struct ArgAttrs<'a> {
855 inner: *mut sys::nix_value,
856 ctx: *mut sys::nix_c_context,
857 state: *mut sys::EvalState,
858 _phantom: PhantomData<&'a ()>,
859}
860
861impl<'a> ArgAttrs<'a> {
867 pub fn get(&self, key: &str) -> Result<PrimOpValue<'a>> {
878 let key_c = CString::new(key)?;
879 let ptr = unsafe {
881 sys::nix_get_attr_byname(self.ctx, self.inner, self.state, key_c.as_ptr())
882 };
883 if ptr.is_null() {
884 return Err(Error::KeyNotFound(key.to_string()));
885 }
886 Ok(PrimOpValue {
887 inner: ptr,
888 ctx: self.ctx,
889 state: self.state,
890 _phantom: PhantomData,
891 })
892 }
893
894 pub fn has(&self, key: &str) -> Result<bool> {
901 let key_c = CString::new(key)?;
902 let result = unsafe {
904 sys::nix_has_attr_byname(self.ctx, self.inner, self.state, key_c.as_ptr())
905 };
906 Ok(result)
907 }
908
909 pub fn keys(&self) -> Result<Vec<String>> {
915 let count = unsafe { sys::nix_get_attrs_size(self.ctx, self.inner) };
917
918 let mut keys = Vec::with_capacity(count as usize);
919 for i in 0..count {
920 let name_ptr = unsafe {
922 sys::nix_get_attr_name_byidx(self.ctx, self.inner, self.state, i)
923 };
924 if name_ptr.is_null() {
925 continue;
926 }
927 let name = unsafe {
928 std::ffi::CStr::from_ptr(name_ptr)
929 .to_string_lossy()
930 .into_owned()
931 };
932 keys.push(name);
933 }
934 Ok(keys)
935 }
936
937 #[must_use]
939 pub fn len(&self) -> usize {
940 unsafe { sys::nix_get_attrs_size(self.ctx, self.inner) as usize }
941 }
942
943 #[must_use]
945 pub fn is_empty(&self) -> bool {
946 self.len() == 0
947 }
948}
949
950pub struct ArgList<'a> {
956 inner: *mut sys::nix_value,
957 ctx: *mut sys::nix_c_context,
958 state: *mut sys::EvalState,
959 _phantom: PhantomData<&'a ()>,
960}
961
962impl<'a> ArgList<'a> {
968 pub fn get(&self, index: usize) -> Result<PrimOpValue<'a>> {
979 let length = self.len();
980 if index >= length {
981 return Err(Error::IndexOutOfBounds { index, length });
982 }
983 let ptr = unsafe {
985 sys::nix_get_list_byidx(
986 self.ctx,
987 self.inner,
988 self.state,
989 index as std::os::raw::c_uint,
990 )
991 };
992 if ptr.is_null() {
993 return Err(Error::NullPointer);
994 }
995 Ok(PrimOpValue {
996 inner: ptr,
997 ctx: self.ctx,
998 state: self.state,
999 _phantom: PhantomData,
1000 })
1001 }
1002
1003 #[must_use]
1005 pub fn len(&self) -> usize {
1006 unsafe { sys::nix_get_list_size(self.ctx, self.inner) as usize }
1007 }
1008
1009 #[must_use]
1011 pub fn is_empty(&self) -> bool {
1012 self.len() == 0
1013 }
1014}
1015
1016pub struct PrimOp {
1025 inner: *mut sys::PrimOp,
1027 context: Arc<Context>,
1029 name: String,
1032 registered: bool,
1034}
1035
1036static REGISTERED_PRIMOPS: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
1040
1041unsafe impl Send for PrimOp {}
1050
1051impl PrimOp {
1052 pub fn new<F>(
1067 context: &Arc<Context>,
1068 name: &str,
1069 arity: u32,
1070 doc: Option<&str>,
1071 f: F,
1072 ) -> Result<Self>
1073 where
1074 F: Fn(&[PrimOpArg<'_>], &mut PrimOpRet<'_>) -> Result<()>
1075 + Send
1076 + Sync
1077 + 'static,
1078 {
1079 let name_c = CString::new(name)?;
1080 let doc_c = doc.map(CString::new).transpose()?;
1081 let empty_doc;
1084 let doc_ptr = match doc_c {
1085 Some(ref c) => c.as_ptr(),
1086 None => {
1087 empty_doc = CString::default();
1088 empty_doc.as_ptr()
1089 },
1090 };
1091
1092 let data = Box::new(ClosureData {
1094 arity: arity as usize,
1095 f: Box::new(f),
1096 });
1097 let data_raw = Box::into_raw(data) as *mut c_void;
1098
1099 let primop_ptr = unsafe {
1102 sys::nix_alloc_primop(
1103 context.as_ptr(),
1104 Some(trampoline),
1105 arity as std::os::raw::c_int,
1106 name_c.as_ptr(),
1107 std::ptr::null_mut(), doc_ptr,
1109 data_raw,
1110 )
1111 };
1112
1113 if primop_ptr.is_null() {
1114 let _ = unsafe { Box::from_raw(data_raw as *mut ClosureData) };
1115 unsafe {
1117 check_err(context.as_ptr(), sys::nix_err_code(context.as_ptr()))?;
1118 }
1119 return Err(Error::NullPointer);
1120 }
1121
1122 unsafe {
1126 sys::nix_gc_register_finalizer(
1127 primop_ptr as *mut c_void,
1128 data_raw,
1129 Some(drop_closure_finalizer),
1130 );
1131 }
1132
1133 Ok(PrimOp {
1134 inner: primop_ptr,
1135 context: Arc::clone(context),
1136 name: name.to_string(),
1137 registered: false,
1138 })
1139 }
1140
1141 pub fn register(mut self, context: &Context) -> Result<()> {
1153 {
1157 let names = REGISTERED_PRIMOPS.get_or_init(|| Mutex::new(HashSet::new()));
1158 let mut guard = names.lock().expect("REGISTERED_PRIMOPS poisoned");
1159 if !guard.insert(self.name.clone()) {
1160 return Err(Error::Unknown(format!(
1161 "primop '{}' is already registered globally",
1162 self.name
1163 )));
1164 }
1165 }
1166
1167 let err = unsafe { sys::nix_register_primop(context.as_ptr(), self.inner) };
1169 check_err(unsafe { self.context.as_ptr() }, err)?;
1170 self.registered = true;
1173 Ok(())
1174 }
1175
1176 pub fn into_value(
1186 self,
1187 state: &crate::EvalState,
1188 ) -> Result<crate::Value<'_>> {
1189 let v = state.alloc_value()?;
1190 unsafe {
1192 check_err(
1193 state.context.as_ptr(),
1194 sys::nix_init_primop(
1195 state.context.as_ptr(),
1196 v.inner.as_ptr(),
1197 self.inner,
1198 ),
1199 )?;
1200 }
1201 Ok(v)
1204 }
1205}
1206
1207impl Drop for PrimOp {
1208 fn drop(&mut self) {
1209 if !self.registered && !self.inner.is_null() {
1210 unsafe {
1215 let _ = sys::nix_gc_decref(
1216 self.context.as_ptr(),
1217 self.inner as *const c_void,
1218 );
1219 }
1220 }
1221 }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226 use std::sync::Arc;
1227
1228 use serial_test::serial;
1229
1230 use super::*;
1231 use crate::{Context, EvalStateBuilder, Store};
1232
1233 #[test]
1234 #[serial]
1235 fn test_primop_into_value() {
1236 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1237 let store =
1238 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1239 let state = EvalStateBuilder::new(&store)
1240 .expect("Failed to create builder")
1241 .build()
1242 .expect("Failed to build state");
1243
1244 let primop =
1246 PrimOp::new(&ctx, "negate", 1, Some("Negate an integer"), |args, ret| {
1247 let n = args[0].as_int()?;
1248 ret.set_int(-n)
1249 })
1250 .expect("Failed to create primop");
1251
1252 let func = primop
1253 .into_value(&state)
1254 .expect("Failed to embed primop as value");
1255
1256 let arg = state.make_int(7).unwrap();
1257 let result = func.call(&arg).expect("Failed to call primop");
1258 assert_eq!(result.as_int().unwrap(), -7);
1259 }
1260
1261 #[test]
1262 #[serial]
1263 fn test_primop_into_value_string() {
1264 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1265 let store =
1266 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1267 let state = EvalStateBuilder::new(&store)
1268 .expect("Failed to create builder")
1269 .build()
1270 .expect("Failed to build state");
1271
1272 let primop = PrimOp::new(&ctx, "hello", 1, None, |_args, ret| {
1274 ret.set_string("hello from primop")
1275 })
1276 .expect("Failed to create primop");
1277
1278 let func = primop
1279 .into_value(&state)
1280 .expect("Failed to embed primop as value");
1281
1282 let arg = state.make_null().unwrap();
1283 let result = func.call(&arg).expect("Failed to call primop");
1284 assert_eq!(result.as_string().unwrap(), "hello from primop");
1285 }
1286
1287 #[test]
1288 #[serial]
1289 fn test_primop_path_roundtrip() {
1290 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1291 let store =
1292 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1293 let state = EvalStateBuilder::new(&store)
1294 .expect("Failed to create builder")
1295 .build()
1296 .expect("Failed to build state");
1297
1298 let primop = PrimOp::new(&ctx, "echo_path", 1, None, |args, ret| {
1302 let p = args[0].as_path()?;
1303 assert_eq!(p, "/tmp/nix-bindings-path-in");
1304 let v = ret.make_path("/tmp/nix-bindings-path-mid")?;
1307 assert_eq!(v.as_path()?, "/tmp/nix-bindings-path-mid");
1308 ret.set_path("/tmp/nix-bindings-path-out")
1309 })
1310 .expect("Failed to create primop");
1311
1312 let func = primop
1313 .into_value(&state)
1314 .expect("Failed to embed primop as value");
1315
1316 let arg = state
1317 .make_path("/tmp/nix-bindings-path-in")
1318 .expect("make_path failed");
1319 let result = func.call(&arg).expect("Failed to call primop");
1320 assert_eq!(result.value_type(), ValueType::Path);
1321 let out = result.as_path().expect("Value::as_path failed");
1322 assert_eq!(out.to_str(), Some("/tmp/nix-bindings-path-out"));
1323 }
1324
1325 #[test]
1326 #[serial]
1327 fn test_primop_arg_as_list() {
1328 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1329 let store =
1330 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1331 let state = EvalStateBuilder::new(&store)
1332 .expect("Failed to create builder")
1333 .build()
1334 .expect("Failed to build state");
1335
1336 let list_val = state
1337 .eval_from_string("[10 20 30]", "<eval>")
1338 .expect("Failed to evaluate list");
1339
1340 let primop = PrimOp::new(&ctx, "list_sum", 1, None, |args, ret| {
1342 let list = args[0].as_list()?;
1343 let mut sum = 0i64;
1344 for i in 0..list.len() {
1345 sum += list.get(i)?.as_int()?;
1346 }
1347 ret.set_int(sum)
1348 })
1349 .expect("Failed to create primop");
1350
1351 let func = primop
1352 .into_value(&state)
1353 .expect("Failed to embed primop as value");
1354
1355 let result = func.call(&list_val).expect("Failed to call primop");
1356 assert_eq!(result.as_int().unwrap(), 60);
1357 }
1358
1359 #[test]
1360 #[serial]
1361 fn test_primop_arg_as_attrs() {
1362 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1363 let store =
1364 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1365 let state = EvalStateBuilder::new(&store)
1366 .expect("Failed to create builder")
1367 .build()
1368 .expect("Failed to build state");
1369
1370 let attrs_val = state
1371 .eval_from_string("{ foo = 42; bar = 13; }", "<eval>")
1372 .expect("Failed to evaluate attrs");
1373
1374 let primop = PrimOp::new(&ctx, "attr_sum", 1, None, |args, ret| {
1376 let attrs = args[0].as_attrs()?;
1377 let foo = attrs.get("foo")?.as_int()?;
1378 let bar = attrs.get("bar")?.as_int()?;
1379 ret.set_int(foo + bar)
1380 })
1381 .expect("Failed to create primop");
1382
1383 let func = primop
1384 .into_value(&state)
1385 .expect("Failed to embed primop as value");
1386
1387 let result = func.call(&attrs_val).expect("Failed to call primop");
1388 assert_eq!(result.as_int().unwrap(), 55);
1389 }
1390
1391 #[test]
1392 #[serial]
1393 fn test_primop_arg_attrs_has_and_keys() {
1394 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1395 let store =
1396 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1397 let state = EvalStateBuilder::new(&store)
1398 .expect("Failed to create builder")
1399 .build()
1400 .expect("Failed to build state");
1401
1402 let attrs_val = state
1403 .eval_from_string("{ a = 1; b = 2; c = 3; }", "<eval>")
1404 .expect("Failed to evaluate attrs");
1405
1406 let primop = PrimOp::new(&ctx, "check_attrs", 1, None, |args, ret| {
1407 let attrs = args[0].as_attrs()?;
1408 assert_eq!(attrs.len(), 3);
1409 assert!(!attrs.is_empty());
1410 assert!(attrs.has("a")?);
1411 assert!(attrs.has("b")?);
1412 assert!(attrs.has("c")?);
1413 assert!(!attrs.has("zzz")?);
1414 let keys = attrs.keys()?;
1415 assert_eq!(keys.len(), 3);
1416 ret.set_null()
1417 })
1418 .expect("Failed to create primop");
1419
1420 let func = primop
1421 .into_value(&state)
1422 .expect("Failed to embed primop as value");
1423
1424 func.call(&attrs_val).expect("Failed to call primop");
1425 }
1426
1427 #[test]
1428 #[serial]
1429 fn test_primop_empty_attrs_and_list() {
1430 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1431 let store =
1432 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1433 let state = EvalStateBuilder::new(&store)
1434 .expect("Failed to create builder")
1435 .build()
1436 .expect("Failed to build state");
1437
1438 let empty_attrs = state
1439 .eval_from_string("{}", "<eval>")
1440 .expect("Failed to evaluate empty attrs");
1441
1442 let primop = PrimOp::new(&ctx, "empty_check", 1, None, |args, ret| {
1443 match args[0].as_attrs() {
1444 Ok(a) => {
1445 assert!(a.is_empty());
1446 assert_eq!(a.len(), 0);
1447 },
1448 Err(_) => {
1449 let list = args[0].as_list()?;
1450 assert!(list.is_empty());
1451 assert_eq!(list.len(), 0);
1452 },
1453 }
1454 ret.set_null()
1455 })
1456 .expect("Failed to create primop");
1457
1458 let func = primop
1459 .into_value(&state)
1460 .expect("Failed to embed primop as value");
1461
1462 func
1463 .call(&empty_attrs)
1464 .expect("call with empty attrs failed");
1465
1466 let empty_list = state
1467 .eval_from_string("[]", "<eval>")
1468 .expect("Failed to evaluate empty list");
1469 func.call(&empty_list).expect("call with empty list failed");
1470 }
1471
1472 #[test]
1473 #[serial]
1474 fn test_primop_ret_set_attrs() {
1475 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1476 let store =
1477 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1478
1479 PrimOp::new(&ctx, "mk_attrs_test", 0, None, |_args, ret| {
1481 let a = ret.make_int(100)?;
1482 let b = ret.make_string("hi")?;
1483 ret.set_attrs(&[("x", &a), ("y", &b)])
1484 })
1485 .expect("Failed to create primop")
1486 .register(&ctx)
1487 .expect("Failed to register primop");
1488
1489 let state = EvalStateBuilder::new(&store)
1490 .expect("Failed to create builder")
1491 .build()
1492 .expect("Failed to build state");
1493
1494 let result = state
1495 .eval_from_string("builtins.mk_attrs_test", "<eval>")
1496 .expect("Failed to evaluate expression");
1497 assert_eq!(result.value_type(), ValueType::Attrs);
1498 assert_eq!(result.attr_keys().unwrap().len(), 2);
1499 let x = result.get_attr("x").expect("missing x");
1500 assert_eq!(x.as_int().unwrap(), 100);
1501 let y = result.get_attr("y").expect("missing y");
1502 assert_eq!(y.as_string().unwrap(), "hi");
1503 }
1504
1505 #[test]
1506 #[serial]
1507 fn test_primop_ret_set_list() {
1508 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1509 let store =
1510 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1511
1512 PrimOp::new(&ctx, "mk_list_test", 0, None, |_args, ret| {
1514 let a = ret.make_int(7)?;
1515 let b = ret.make_string("hi")?;
1516 let c = ret.make_bool(true)?;
1517 ret.set_list(&[&a, &b, &c])
1518 })
1519 .expect("Failed to create primop")
1520 .register(&ctx)
1521 .expect("Failed to register primop");
1522
1523 let state = EvalStateBuilder::new(&store)
1524 .expect("Failed to create builder")
1525 .build()
1526 .expect("Failed to build state");
1527
1528 let result = state
1529 .eval_from_string("builtins.mk_list_test", "<eval>")
1530 .expect("Failed to evaluate expression");
1531 assert_eq!(result.value_type(), ValueType::List);
1532 assert_eq!(result.list_len().unwrap(), 3);
1533
1534 let first = result.list_get(0).unwrap();
1535 assert_eq!(first.as_int().unwrap(), 7);
1536 let second = result.list_get(1).unwrap();
1537 assert_eq!(second.as_string().unwrap(), "hi");
1538 let third = result.list_get(2).unwrap();
1539 assert!(third.as_bool().unwrap());
1540 }
1541
1542 #[test]
1543 #[serial]
1544 fn test_primop_ret_make_types() {
1545 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1546 let store =
1547 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1548
1549 PrimOp::new(&ctx, "mk_types_test", 0, None, |_args, ret| {
1551 let int_val = ret.make_int(-42)?;
1552 assert_eq!(int_val.value_type(), ValueType::Int);
1553 assert_eq!(int_val.as_int()?, -42);
1554
1555 let float_val = ret.make_float(2.5)?;
1556 assert_eq!(float_val.value_type(), ValueType::Float);
1557 assert!((float_val.as_float()? - 2.5).abs() < 1e-9);
1558
1559 let bool_val = ret.make_bool(true)?;
1560 assert_eq!(bool_val.value_type(), ValueType::Bool);
1561 assert!(bool_val.as_bool()?);
1562
1563 let null_val = ret.make_null()?;
1564 assert_eq!(null_val.value_type(), ValueType::Null);
1565
1566 let str_val = ret.make_string("ok")?;
1567 assert_eq!(str_val.value_type(), ValueType::String);
1568 assert_eq!(str_val.as_string()?, "ok");
1569
1570 ret.set_int(0)
1571 })
1572 .expect("Failed to create primop")
1573 .register(&ctx)
1574 .expect("Failed to register primop");
1575
1576 let state = EvalStateBuilder::new(&store)
1577 .expect("Failed to create builder")
1578 .build()
1579 .expect("Failed to build state");
1580
1581 let result = state
1582 .eval_from_string("builtins.mk_types_test", "<eval>")
1583 .expect("Failed to evaluate expression");
1584 assert_eq!(result.as_int().unwrap(), 0);
1585 }
1586
1587 #[test]
1588 #[serial]
1589 fn test_primop_value_as_attrs_chained() {
1590 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1591 let store =
1592 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1593 let state = EvalStateBuilder::new(&store)
1594 .expect("Failed to create builder")
1595 .build()
1596 .expect("Failed to build state");
1597
1598 let nested = state
1599 .eval_from_string("{ inner = { x = 99; }; }", "<eval>")
1600 .expect("Failed to evaluate nested attrs");
1601
1602 let primop = PrimOp::new(&ctx, "nested_get", 1, None, |args, ret| {
1603 let outer = args[0].as_attrs()?;
1604 let inner = outer.get("inner")?;
1605 let inner_attrs = inner.as_attrs()?;
1606 let x = inner_attrs.get("x")?.as_int()?;
1607 ret.set_int(x)
1608 })
1609 .expect("Failed to create primop");
1610
1611 let func = primop
1612 .into_value(&state)
1613 .expect("Failed to embed primop as value");
1614
1615 let result = func.call(&nested).expect("Failed to call primop");
1616 assert_eq!(result.as_int().unwrap(), 99);
1617 }
1618
1619 #[test]
1620 #[serial]
1621 fn test_primop_unwritten_slot_is_diagnosed() {
1622 let ctx = Arc::new(Context::new().expect("Failed to create context"));
1623 let store =
1624 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
1625 let state = EvalStateBuilder::new(&store)
1626 .expect("Failed to create builder")
1627 .build()
1628 .expect("Failed to build state");
1629
1630 let primop =
1634 PrimOp::new(&ctx, "broken_primop", 1, None, |_args, _ret| Ok(()))
1635 .expect("Failed to create primop");
1636 let func = primop
1637 .into_value(&state)
1638 .expect("Failed to embed primop as value");
1639 let arg = state.make_int(1).expect("Failed to make int");
1640 let result = func.call(&arg);
1641 assert!(
1642 result.is_err(),
1643 "Expected an error when the primop returns without writing the slot, \
1644 got {:?}",
1645 result.map(|v| v.value_type()),
1646 );
1647 }
1648}