1use std::{
45 any::TypeId,
46 ffi::CString,
47 ops::Deref,
48 panic::{self, AssertUnwindSafe},
49};
50
51use crate::{Error, EvalState, Result, Value, sys};
52
53pub trait NixExternal: Send + Sync + 'static {
60 fn display(&self) -> String;
64
65 fn type_name(&self) -> &'static str;
67
68 fn coerce_to_string(&self) -> Option<String> {
73 None
74 }
75
76 fn equal(&self, _other: &Self) -> bool {
80 false
81 }
82}
83
84const ERASED_PAYLOAD_MAGIC: u64 = 0x4E49585F455854; #[repr(C)]
98struct ErasedPayload {
99 magic: u64,
100 type_id: TypeId,
101
102 data: *mut std::os::raw::c_void,
104
105 drop_fn: unsafe fn(*mut std::os::raw::c_void),
107
108 display_fn: unsafe fn(*const std::os::raw::c_void) -> String,
110
111 type_name_fn: unsafe fn(*const std::os::raw::c_void) -> &'static str,
113
114 coerce_fn: unsafe fn(*const std::os::raw::c_void) -> Option<String>,
116
117 equal_fn:
119 unsafe fn(*const std::os::raw::c_void, *const std::os::raw::c_void) -> bool,
120}
121
122unsafe fn drop_erased<T>(ptr: *mut std::os::raw::c_void) {
123 drop(unsafe { Box::from_raw(ptr.cast::<T>()) });
124}
125
126unsafe fn display_erased<T: NixExternal>(
127 ptr: *const std::os::raw::c_void,
128) -> String {
129 let t = unsafe { &*(ptr as *const T) };
130 t.display()
131}
132
133unsafe fn type_name_erased<T: NixExternal>(
134 ptr: *const std::os::raw::c_void,
135) -> &'static str {
136 let t = unsafe { &*(ptr as *const T) };
137 t.type_name()
138}
139
140unsafe fn coerce_erased<T: NixExternal>(
141 ptr: *const std::os::raw::c_void,
142) -> Option<String> {
143 let t = unsafe { &*(ptr as *const T) };
144 t.coerce_to_string()
145}
146
147unsafe fn equal_erased<T: NixExternal>(
148 ptr: *const std::os::raw::c_void,
149 other: *const std::os::raw::c_void,
150) -> bool {
151 let a = unsafe { &*(ptr as *const T) };
152 let b = unsafe { &*(other as *const T) };
153 a.equal(b)
154}
155
156impl ErasedPayload {
157 fn new<T: NixExternal>(value: T) -> *mut Self {
158 let data_box = Box::new(value);
159 let data_ptr = Box::into_raw(data_box) as *mut std::os::raw::c_void;
160
161 Box::into_raw(Box::new(ErasedPayload {
162 magic: ERASED_PAYLOAD_MAGIC,
163 type_id: TypeId::of::<T>(),
164 data: data_ptr,
165 drop_fn: drop_erased::<T>,
166 display_fn: display_erased::<T>,
167 type_name_fn: type_name_erased::<T>,
168 coerce_fn: coerce_erased::<T>,
169 equal_fn: equal_erased::<T>,
170 }))
171 }
172
173 unsafe fn try_from_void<'a>(
178 ptr: *mut std::os::raw::c_void,
179 ) -> Option<&'a Self> {
180 if ptr.is_null() {
181 return None;
182 }
183 let candidate = unsafe { &*(ptr as *const ErasedPayload) };
184 if candidate.magic != ERASED_PAYLOAD_MAGIC {
185 return None;
186 }
187 Some(candidate)
188 }
189
190 unsafe fn from_void<'a>(ptr: *mut std::os::raw::c_void) -> &'a Self {
194 unsafe { Self::try_from_void(ptr) }
195 .expect("ErasedPayload sentinel mismatch")
196 }
197}
198
199impl Drop for ErasedPayload {
200 fn drop(&mut self) {
201 unsafe { (self.drop_fn)(self.data) };
204 }
205}
206
207static VTABLE: sys::NixCExternalValueDesc = {
212 unsafe extern "C" fn print(
213 self_: *mut std::os::raw::c_void,
214 printer: *mut sys::nix_printer,
215 ) {
216 let _ = panic::catch_unwind(AssertUnwindSafe(|| {
217 let payload = unsafe { ErasedPayload::from_void(self_) };
218 let s = unsafe { (payload.display_fn)(payload.data) };
219 if let Ok(cs) = CString::new(s) {
220 unsafe {
221 sys::nix_external_print(std::ptr::null_mut(), printer, cs.as_ptr());
222 }
223 }
224 }));
225 }
226
227 unsafe extern "C" fn show_type(
228 self_: *mut std::os::raw::c_void,
229 res: *mut sys::nix_string_return,
230 ) {
231 let _ = panic::catch_unwind(AssertUnwindSafe(|| {
232 let payload = unsafe { ErasedPayload::from_void(self_) };
233 let name = unsafe { (payload.type_name_fn)(payload.data) };
234 if let Ok(cs) = CString::new(name) {
235 unsafe { sys::nix_set_string_return(res, cs.as_ptr()) };
236 }
237 }));
238 }
239
240 unsafe extern "C" fn type_of(
241 _self: *mut std::os::raw::c_void,
242 res: *mut sys::nix_string_return,
243 ) {
244 let _ = panic::catch_unwind(AssertUnwindSafe(|| {
245 if let Ok(cs) = CString::new("nix-external") {
247 unsafe { sys::nix_set_string_return(res, cs.as_ptr()) };
248 }
249 }));
250 }
251
252 unsafe extern "C" fn coerce_to_string(
253 self_: *mut std::os::raw::c_void,
254 _c: *mut sys::nix_string_context,
255 _coerce_more: std::os::raw::c_int,
256 _copy_to_store: std::os::raw::c_int,
257 res: *mut sys::nix_string_return,
258 ) {
259 let _ = panic::catch_unwind(AssertUnwindSafe(|| {
260 let payload = unsafe { ErasedPayload::from_void(self_) };
261 if let Some(s) = unsafe { (payload.coerce_fn)(payload.data) }
262 && let Ok(cs) = CString::new(s)
263 {
264 unsafe { sys::nix_set_string_return(res, cs.as_ptr()) };
265 }
266 }));
267 }
268
269 unsafe extern "C" fn equal(
270 self_: *mut std::os::raw::c_void,
271 other: *mut std::os::raw::c_void,
272 ) -> std::os::raw::c_int {
273 let result = panic::catch_unwind(AssertUnwindSafe(|| {
274 let a = match unsafe { ErasedPayload::try_from_void(self_) } {
275 Some(p) => p,
276 None => return 0,
277 };
278 let b = match unsafe { ErasedPayload::try_from_void(other) } {
279 Some(p) => p,
280 None => return 0,
281 };
282 if a.type_id != b.type_id {
284 return 0;
285 }
286 if unsafe { (a.equal_fn)(a.data, b.data) } {
287 1
288 } else {
289 0
290 }
291 }));
292 result.unwrap_or(0)
293 }
294
295 sys::NixCExternalValueDesc {
297 print: Some(print),
298 showType: Some(show_type),
299 typeOf: Some(type_of),
300 coerceToString: Some(coerce_to_string),
301 equal: Some(equal),
302 printValueAsJSON: None,
303 printValueAsXML: None,
304 }
305};
306
307impl EvalState {
308 pub fn make_external<T: NixExternal>(
318 &self,
319 data: T,
320 ) -> Result<ExternalValueHandle<'_>> {
321 let payload_ptr = ErasedPayload::new(data);
322 let vtable_ptr = &VTABLE as *const sys::NixCExternalValueDesc
325 as *mut sys::NixCExternalValueDesc;
326
327 let v = self.alloc_value()?;
329
330 let ext_ptr = unsafe {
333 sys::nix_create_external_value(
334 self.context.as_ptr(),
335 vtable_ptr,
336 payload_ptr.cast::<std::os::raw::c_void>(),
337 )
338 };
339
340 if ext_ptr.is_null() {
341 drop(unsafe { Box::from_raw(payload_ptr) });
342 return Err(Error::NullPointer);
343 }
344
345 unsafe {
347 crate::check_err(
348 self.context.as_ptr(),
349 sys::nix_init_external(
350 self.context.as_ptr(),
351 v.inner.as_ptr(),
352 ext_ptr,
353 ),
354 )?;
355 }
356
357 Ok(ExternalValueHandle { value: v, ext_ptr })
358 }
359}
360
361pub struct ExternalValueHandle<'s> {
369 value: Value<'s>,
370 ext_ptr: *mut sys::ExternalValue,
371}
372
373unsafe impl Send for ExternalValueHandle<'_> {}
382
383impl<'s> Deref for ExternalValueHandle<'s> {
384 type Target = Value<'s>;
385
386 fn deref(&self) -> &Self::Target {
387 &self.value
388 }
389}
390
391impl ExternalValueHandle<'_> {
392 pub fn as_external<T: NixExternal>(&self) -> Result<&T> {
399 let void_ptr = unsafe {
402 sys::nix_get_external_value_content(
403 self.value.state.context.as_ptr(),
404 self.ext_ptr,
405 )
406 };
407
408 if void_ptr.is_null() {
409 return Err(Error::NullPointer);
410 }
411
412 let payload = unsafe { ErasedPayload::from_void(void_ptr) };
415
416 if payload.type_id != TypeId::of::<T>() {
417 return Err(Error::InvalidType {
418 expected: std::any::type_name::<T>(),
419 actual: "external value of different type".to_string(),
420 });
421 }
422
423 let t_ref = unsafe { &*(payload.data as *const T) };
425 Ok(t_ref)
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use std::sync::Arc;
432
433 use serial_test::serial;
434
435 use super::*;
436 use crate::{Context, EvalStateBuilder, Store, ValueType};
437
438 struct MyData(i64);
439
440 impl NixExternal for MyData {
441 fn display(&self) -> String {
442 format!("MyData({})", self.0)
443 }
444
445 fn type_name(&self) -> &'static str {
446 "MyData"
447 }
448 }
449
450 fn make_eval_state() -> (Arc<Context>, Arc<Store>, crate::EvalState) {
451 let ctx = Arc::new(Context::new().expect("Failed to create context"));
452 let store =
453 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
454 let state = EvalStateBuilder::new(&store)
455 .expect("Failed to create builder")
456 .build()
457 .expect("Failed to build state");
458 (ctx, store, state)
459 }
460
461 #[test]
462 #[serial]
463 fn test_make_and_recover_external() {
464 let (_ctx, _store, state) = make_eval_state();
465
466 let handle = state
467 .make_external(MyData(42))
468 .expect("make_external failed");
469 assert_eq!(handle.value_type(), ValueType::External);
470
471 let back = handle.as_external::<MyData>().expect("as_external failed");
472 assert_eq!(back.0, 42);
473 }
474
475 #[test]
476 #[serial]
477 fn test_wrong_type_returns_error() {
478 let (_ctx, _store, state) = make_eval_state();
479
480 struct OtherData;
481 impl NixExternal for OtherData {
482 fn display(&self) -> String {
483 "OtherData".to_string()
484 }
485
486 fn type_name(&self) -> &'static str {
487 "OtherData"
488 }
489 }
490
491 let handle = state
492 .make_external(MyData(1))
493 .expect("make_external failed");
494 let result = handle.as_external::<OtherData>();
495 assert!(
496 result.is_err(),
497 "Downcasting to wrong type should return Err"
498 );
499 }
500
501 #[test]
502 #[serial]
503 fn test_as_external_on_non_external_value() {
504 let (_ctx, _store, state) = make_eval_state();
505
506 let int_val = state.make_int(5).expect("make_int failed");
507 assert_ne!(int_val.value_type(), ValueType::External);
510 }
511}