nix_bindings/
lib.rs

1#![warn(missing_docs)]
2//! High-level, safe Rust bindings for the Nix build tool.
3//!
4//! This crate provides ergonomic and idiomatic Rust APIs for interacting
5//! with Nix using its C API.
6//!
7//! # Quick Start
8//!
9//! ```no_run
10//! use std::sync::Arc;
11//!
12//! use nix_bindings::{Context, EvalStateBuilder, Store};
13//!
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! let ctx = Arc::new(Context::new()?);
16//! let store = Arc::new(Store::open(&ctx, None)?);
17//! let state = EvalStateBuilder::new(&store)?.build()?;
18//!
19//! let result = state.eval_from_string("1 + 2", "<eval>")?;
20//! println!("Result: {}", result.as_int()?);
21//! # Ok(())
22//! # }
23//! ```
24//!
25//! # Value Formatting
26//!
27//! Values support multiple formatting options:
28//!
29//! ```no_run
30//! # use std::sync::Arc;
31//! # use nix_bindings::{Context, EvalStateBuilder, Store};
32//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! # let ctx = Arc::new(Context::new()?);
34//! # let store = Arc::new(Store::open(&ctx, None)?);
35//! # let state = EvalStateBuilder::new(&store)?.build()?;
36//! let value = state.eval_from_string("\"hello world\"", "<eval>")?;
37//!
38//! // Display formatting (user-friendly)
39//! println!("{}", value); // Output: hello world
40//!
41//! // Debug formatting (with type info)
42//! println!("{:?}", value); // Output: Value::String("hello world")
43//!
44//! // Nix syntax formatting
45//! println!("{}", value.to_nix_string()?); // Output: "hello world"
46//! //
47//! # Ok(())
48//! # }
49//! ```
50
51mod store;
52
53use std::{ffi::CString, fmt, ptr::NonNull, sync::Arc};
54
55#[cfg(test)] use serial_test::serial;
56pub use store::{Store, StorePath};
57
58/// Raw, unsafe FFI bindings to the Nix C API.
59///
60/// # Warning
61///
62/// This module exposes the low-level, unsafe C bindings. Prefer using the safe,
63/// high-level APIs provided by this crate. Use at your own risk.
64#[doc(hidden)]
65pub mod sys {
66  pub use nix_bindings_sys::*;
67}
68
69/// Result type for Nix operations.
70pub type Result<T> = std::result::Result<T, Error>;
71
72/// Error types for Nix operations.
73#[derive(Debug)]
74pub enum Error {
75  /// Unknown error from Nix C API.
76  Unknown(String),
77
78  /// Overflow error.
79  Overflow,
80
81  /// Key not found error.
82  KeyNotFound(String),
83
84  /// Nix evaluation error.
85  EvalError(String),
86
87  /// Invalid value type conversion.
88  InvalidType {
89    /// Expected type.
90    expected: &'static str,
91    /// Actual type.
92    actual:   String,
93  },
94  /// Null pointer error.
95  NullPointer,
96
97  /// String conversion error.
98  StringConversion(std::ffi::NulError),
99}
100
101impl fmt::Display for Error {
102  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103    match self {
104      Error::Unknown(msg) => write!(f, "Unknown error: {msg}"),
105      Error::Overflow => write!(f, "Overflow error"),
106      Error::KeyNotFound(key) => write!(f, "Key not found: {key}"),
107      Error::EvalError(msg) => write!(f, "Evaluation error: {msg}"),
108      Error::InvalidType { expected, actual } => {
109        write!(f, "Invalid type: expected {expected}, got {actual}")
110      },
111      Error::NullPointer => write!(f, "Null pointer error"),
112      Error::StringConversion(e) => write!(f, "String conversion error: {e}"),
113    }
114  }
115}
116
117impl std::error::Error for Error {}
118
119impl From<std::ffi::NulError> for Error {
120  fn from(e: std::ffi::NulError) -> Self {
121    Error::StringConversion(e)
122  }
123}
124
125/// Check a Nix error code and convert to Result.
126fn check_err(err: sys::nix_err) -> Result<()> {
127  match err {
128    sys::nix_err_NIX_OK => Ok(()),
129    sys::nix_err_NIX_ERR_UNKNOWN => {
130      Err(Error::Unknown("Unknown error".to_string()))
131    },
132    sys::nix_err_NIX_ERR_OVERFLOW => Err(Error::Overflow),
133    sys::nix_err_NIX_ERR_KEY => {
134      Err(Error::KeyNotFound("Key not found".to_string()))
135    },
136    sys::nix_err_NIX_ERR_NIX_ERROR => {
137      Err(Error::EvalError("Evaluation error".to_string()))
138    },
139    _ => Err(Error::Unknown(format!("Error code: {err}"))),
140  }
141}
142
143/// Nix context for managing library state.
144///
145/// This is the root object for all Nix operations. It manages the lifetime
146/// of the Nix C API context and provides automatic cleanup.
147pub struct Context {
148  inner: NonNull<sys::nix_c_context>,
149}
150
151impl Context {
152  /// Create a new Nix context.
153  ///
154  /// This initializes the Nix C API context and the required libraries.
155  ///
156  /// # Errors
157  ///
158  /// Returns an error if context creation or library initialization fails.
159  pub fn new() -> Result<Self> {
160    // SAFETY: nix_c_context_create is safe to call
161    let ctx_ptr = unsafe { sys::nix_c_context_create() };
162    let inner = NonNull::new(ctx_ptr).ok_or(Error::NullPointer)?;
163
164    let ctx = Context { inner };
165
166    // Initialize required libraries
167    unsafe {
168      check_err(sys::nix_libutil_init(ctx.inner.as_ptr()))?;
169      check_err(sys::nix_libstore_init(ctx.inner.as_ptr()))?;
170      check_err(sys::nix_libexpr_init(ctx.inner.as_ptr()))?;
171    }
172
173    Ok(ctx)
174  }
175
176  /// Get the raw context pointer.
177  ///
178  /// # Safety
179  ///
180  /// The caller must ensure the pointer is used safely.
181  unsafe fn as_ptr(&self) -> *mut sys::nix_c_context {
182    self.inner.as_ptr()
183  }
184}
185
186impl Drop for Context {
187  fn drop(&mut self) {
188    // SAFETY: We own the context and it's valid until drop
189    unsafe {
190      sys::nix_c_context_free(self.inner.as_ptr());
191    }
192  }
193}
194
195// SAFETY: Context can be shared between threads
196unsafe impl Send for Context {}
197unsafe impl Sync for Context {}
198
199/// Builder for Nix evaluation state.
200///
201/// This allows configuring the evaluation environment before creating
202/// the evaluation state.
203pub struct EvalStateBuilder {
204  inner:   NonNull<sys::nix_eval_state_builder>,
205  store:   Arc<Store>,
206  context: Arc<Context>,
207}
208
209impl EvalStateBuilder {
210  /// Create a new evaluation state builder.
211  ///
212  /// # Arguments
213  ///
214  /// * `store` - The Nix store to use for evaluation
215  ///
216  /// # Errors
217  ///
218  /// Returns an error if the builder cannot be created.
219  pub fn new(store: &Arc<Store>) -> Result<Self> {
220    // SAFETY: store context and store are valid
221    let builder_ptr = unsafe {
222      sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr())
223    };
224
225    let inner = NonNull::new(builder_ptr).ok_or(Error::NullPointer)?;
226
227    Ok(EvalStateBuilder {
228      inner,
229      store: Arc::clone(store),
230      context: Arc::clone(&store._context),
231    })
232  }
233
234  /// Build the evaluation state.
235  ///
236  /// # Errors
237  ///
238  /// Returns an error if the evaluation state cannot be built.
239  pub fn build(self) -> Result<EvalState> {
240    // Load configuration first
241    // SAFETY: context and builder are valid
242    unsafe {
243      check_err(sys::nix_eval_state_builder_load(
244        self.context.as_ptr(),
245        self.inner.as_ptr(),
246      ))?;
247    }
248
249    // Build the state
250    // SAFETY: context and builder are valid
251    let state_ptr = unsafe {
252      sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr())
253    };
254
255    let inner = NonNull::new(state_ptr).ok_or(Error::NullPointer)?;
256
257    // The builder is consumed here - its Drop will clean up
258    Ok(EvalState {
259      inner,
260      store: self.store.clone(),
261      context: self.context.clone(),
262    })
263  }
264}
265
266impl Drop for EvalStateBuilder {
267  fn drop(&mut self) {
268    // SAFETY: We own the builder and it's valid until drop
269    unsafe {
270      sys::nix_eval_state_builder_free(self.inner.as_ptr());
271    }
272  }
273}
274
275/// Nix evaluation state for evaluating expressions.
276///
277/// This provides the main interface for evaluating Nix expressions
278/// and creating values.
279pub struct EvalState {
280  inner:   NonNull<sys::EvalState>,
281  #[allow(dead_code)]
282  store:   Arc<Store>,
283  context: Arc<Context>,
284}
285
286impl EvalState {
287  /// Evaluate a Nix expression from a string.
288  ///
289  /// # Arguments
290  ///
291  /// * `expr` - The Nix expression to evaluate
292  /// * `path` - The path to use for error reporting (e.g., "<eval>")
293  ///
294  /// # Errors
295  ///
296  /// Returns an error if evaluation fails.
297  pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value<'_>> {
298    let expr_c = CString::new(expr)?;
299    let path_c = CString::new(path)?;
300
301    // Allocate value for result
302    // SAFETY: context and state are valid
303    let value_ptr = unsafe {
304      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
305    };
306    if value_ptr.is_null() {
307      return Err(Error::NullPointer);
308    }
309
310    // Evaluate expression
311    // SAFETY: all pointers are valid
312    unsafe {
313      check_err(sys::nix_expr_eval_from_string(
314        self.context.as_ptr(),
315        self.inner.as_ptr(),
316        expr_c.as_ptr(),
317        path_c.as_ptr(),
318        value_ptr,
319      ))?;
320    }
321
322    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
323
324    Ok(Value { inner, state: self })
325  }
326
327  /// Allocate a new value.
328  ///
329  /// # Errors
330  ///
331  /// Returns an error if value allocation fails.
332  pub fn alloc_value(&self) -> Result<Value<'_>> {
333    // SAFETY: context and state are valid
334    let value_ptr = unsafe {
335      sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr())
336    };
337    let inner = NonNull::new(value_ptr).ok_or(Error::NullPointer)?;
338
339    Ok(Value { inner, state: self })
340  }
341
342  /// Get the raw state pointer.
343  ///
344  /// # Safety
345  ///
346  /// The caller must ensure the pointer is used safely.
347  unsafe fn as_ptr(&self) -> *mut sys::EvalState {
348    self.inner.as_ptr()
349  }
350}
351
352impl Drop for EvalState {
353  fn drop(&mut self) {
354    // SAFETY: We own the state and it's valid until drop
355    unsafe {
356      sys::nix_state_free(self.inner.as_ptr());
357    }
358  }
359}
360
361// SAFETY: EvalState can be shared between threads
362unsafe impl Send for EvalState {}
363unsafe impl Sync for EvalState {}
364
365/// Nix value types.
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367pub enum ValueType {
368  /// Thunk (unevaluated expression).
369  Thunk,
370  /// Integer value.
371  Int,
372  /// Float value.
373  Float,
374  /// Boolean value.
375  Bool,
376  /// String value.
377  String,
378  /// Path value.
379  Path,
380  /// Null value.
381  Null,
382  /// Attribute set.
383  Attrs,
384  /// List.
385  List,
386  /// Function.
387  Function,
388  /// External value.
389  External,
390}
391
392impl ValueType {
393  fn from_c(value_type: sys::ValueType) -> Self {
394    match value_type {
395      sys::ValueType_NIX_TYPE_THUNK => ValueType::Thunk,
396      sys::ValueType_NIX_TYPE_INT => ValueType::Int,
397      sys::ValueType_NIX_TYPE_FLOAT => ValueType::Float,
398      sys::ValueType_NIX_TYPE_BOOL => ValueType::Bool,
399      sys::ValueType_NIX_TYPE_STRING => ValueType::String,
400      sys::ValueType_NIX_TYPE_PATH => ValueType::Path,
401      sys::ValueType_NIX_TYPE_NULL => ValueType::Null,
402      sys::ValueType_NIX_TYPE_ATTRS => ValueType::Attrs,
403      sys::ValueType_NIX_TYPE_LIST => ValueType::List,
404      sys::ValueType_NIX_TYPE_FUNCTION => ValueType::Function,
405      sys::ValueType_NIX_TYPE_EXTERNAL => ValueType::External,
406      _ => ValueType::Thunk, // fallback
407    }
408  }
409}
410
411impl fmt::Display for ValueType {
412  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413    let name = match self {
414      ValueType::Thunk => "thunk",
415      ValueType::Int => "int",
416      ValueType::Float => "float",
417      ValueType::Bool => "bool",
418      ValueType::String => "string",
419      ValueType::Path => "path",
420      ValueType::Null => "null",
421      ValueType::Attrs => "attrs",
422      ValueType::List => "list",
423      ValueType::Function => "function",
424      ValueType::External => "external",
425    };
426    write!(f, "{name}")
427  }
428}
429
430/// A Nix value.
431///
432/// This represents any value in the Nix language, including primitives,
433/// collections, and functions.
434pub struct Value<'a> {
435  inner: NonNull<sys::nix_value>,
436  state: &'a EvalState,
437}
438
439impl Value<'_> {
440  /// Force evaluation of this value.
441  ///
442  /// If the value is a thunk, this will evaluate it to its final form.
443  ///
444  /// # Errors
445  ///
446  /// Returns an error if evaluation fails.
447  pub fn force(&mut self) -> Result<()> {
448    // SAFETY: context, state, and value are valid
449    unsafe {
450      check_err(sys::nix_value_force(
451        self.state.context.as_ptr(),
452        self.state.as_ptr(),
453        self.inner.as_ptr(),
454      ))
455    }
456  }
457
458  /// Force deep evaluation of this value.
459  ///
460  /// This forces evaluation of the value and all its nested components.
461  ///
462  /// # Errors
463  ///
464  /// Returns an error if evaluation fails.
465  pub fn force_deep(&mut self) -> Result<()> {
466    // SAFETY: context, state, and value are valid
467    unsafe {
468      check_err(sys::nix_value_force_deep(
469        self.state.context.as_ptr(),
470        self.state.as_ptr(),
471        self.inner.as_ptr(),
472      ))
473    }
474  }
475
476  /// Get the type of this value.
477  #[must_use]
478  pub fn value_type(&self) -> ValueType {
479    // SAFETY: context and value are valid
480    let c_type = unsafe {
481      sys::nix_get_type(self.state.context.as_ptr(), self.inner.as_ptr())
482    };
483    ValueType::from_c(c_type)
484  }
485
486  /// Convert this value to an integer.
487  ///
488  /// # Errors
489  ///
490  /// Returns an error if the value is not an integer.
491  pub fn as_int(&self) -> Result<i64> {
492    if self.value_type() != ValueType::Int {
493      return Err(Error::InvalidType {
494        expected: "int",
495        actual:   self.value_type().to_string(),
496      });
497    }
498
499    // SAFETY: context and value are valid, type is checked
500    let result = unsafe {
501      sys::nix_get_int(self.state.context.as_ptr(), self.inner.as_ptr())
502    };
503
504    Ok(result)
505  }
506
507  /// Convert this value to a float.
508  ///
509  /// # Errors
510  ///
511  /// Returns an error if the value is not a float.
512  pub fn as_float(&self) -> Result<f64> {
513    if self.value_type() != ValueType::Float {
514      return Err(Error::InvalidType {
515        expected: "float",
516        actual:   self.value_type().to_string(),
517      });
518    }
519
520    // SAFETY: context and value are valid, type is checked
521    let result = unsafe {
522      sys::nix_get_float(self.state.context.as_ptr(), self.inner.as_ptr())
523    };
524
525    Ok(result)
526  }
527
528  /// Convert this value to a boolean.
529  ///
530  /// # Errors
531  ///
532  /// Returns an error if the value is not a boolean.
533  pub fn as_bool(&self) -> Result<bool> {
534    if self.value_type() != ValueType::Bool {
535      return Err(Error::InvalidType {
536        expected: "bool",
537        actual:   self.value_type().to_string(),
538      });
539    }
540
541    // SAFETY: context and value are valid, type is checked
542    let result = unsafe {
543      sys::nix_get_bool(self.state.context.as_ptr(), self.inner.as_ptr())
544    };
545
546    Ok(result)
547  }
548
549  /// Convert this value to a string.
550  ///
551  /// # Errors
552  ///
553  /// Returns an error if the value is not a string.
554  pub fn as_string(&self) -> Result<String> {
555    if self.value_type() != ValueType::String {
556      return Err(Error::InvalidType {
557        expected: "string",
558        actual:   self.value_type().to_string(),
559      });
560    }
561
562    // For string values, we need to use realised string API
563    // SAFETY: context and value are valid, type is checked
564    let realised_str = unsafe {
565      sys::nix_string_realise(
566        self.state.context.as_ptr(),
567        self.state.as_ptr(),
568        self.inner.as_ptr(),
569        false, // don't copy more
570      )
571    };
572
573    if realised_str.is_null() {
574      return Err(Error::NullPointer);
575    }
576
577    // SAFETY: realised_str is non-null and points to valid RealizedString
578    let buffer_start =
579      unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
580
581    let buffer_size =
582      unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
583
584    if buffer_start.is_null() {
585      // Clean up realised string
586      unsafe {
587        sys::nix_realised_string_free(realised_str);
588      }
589      return Err(Error::NullPointer);
590    }
591
592    // SAFETY: buffer_start is non-null and buffer_size gives us the length
593    let bytes = unsafe {
594      std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size)
595    };
596    let string = std::str::from_utf8(bytes)
597      .map_err(|_| Error::Unknown("Invalid UTF-8 in string".to_string()))?
598      .to_owned();
599
600    // Clean up realised string
601    unsafe {
602      sys::nix_realised_string_free(realised_str);
603    }
604
605    Ok(string)
606  }
607
608  /// Get the raw value pointer.
609  ///
610  /// # Safety
611  ///
612  /// The caller must ensure the pointer is used safely.
613  #[allow(dead_code)]
614  unsafe fn as_ptr(&self) -> *mut sys::nix_value {
615    self.inner.as_ptr()
616  }
617
618  /// Format this value as Nix syntax.
619  ///
620  /// This provides a string representation that matches Nix's own syntax,
621  /// making it useful for debugging and displaying values to users.
622  ///
623  /// # Errors
624  ///
625  /// Returns an error if the value cannot be converted to a string
626  /// representation.
627  pub fn to_nix_string(&self) -> Result<String> {
628    match self.value_type() {
629      ValueType::Int => Ok(self.as_int()?.to_string()),
630      ValueType::Float => Ok(self.as_float()?.to_string()),
631      ValueType::Bool => {
632        Ok(if self.as_bool()? {
633          "true".to_string()
634        } else {
635          "false".to_string()
636        })
637      },
638      ValueType::String => {
639        Ok(format!("\"{}\"", self.as_string()?.replace('"', "\\\"")))
640      },
641      ValueType::Null => Ok("null".to_string()),
642      ValueType::Attrs => Ok("{ <attrs> }".to_string()),
643      ValueType::List => Ok("[ <list> ]".to_string()),
644      ValueType::Function => Ok("<function>".to_string()),
645      ValueType::Path => Ok("<path>".to_string()),
646      ValueType::Thunk => Ok("<thunk>".to_string()),
647      ValueType::External => Ok("<external>".to_string()),
648    }
649  }
650}
651
652impl Drop for Value<'_> {
653  fn drop(&mut self) {
654    // SAFETY: We own the value and it's valid until drop
655    unsafe {
656      sys::nix_value_decref(self.state.context.as_ptr(), self.inner.as_ptr());
657    }
658  }
659}
660
661impl fmt::Display for Value<'_> {
662  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
663    match self.value_type() {
664      ValueType::Int => {
665        if let Ok(val) = self.as_int() {
666          write!(f, "{val}")
667        } else {
668          write!(f, "<int error>")
669        }
670      },
671      ValueType::Float => {
672        if let Ok(val) = self.as_float() {
673          write!(f, "{val}")
674        } else {
675          write!(f, "<float error>")
676        }
677      },
678      ValueType::Bool => {
679        if let Ok(val) = self.as_bool() {
680          write!(f, "{val}")
681        } else {
682          write!(f, "<bool error>")
683        }
684      },
685      ValueType::String => {
686        if let Ok(val) = self.as_string() {
687          write!(f, "{val}")
688        } else {
689          write!(f, "<string error>")
690        }
691      },
692      ValueType::Null => write!(f, "null"),
693      ValueType::Attrs => write!(f, "{{ <attrs> }}"),
694      ValueType::List => write!(f, "[ <list> ]"),
695      ValueType::Function => write!(f, "<function>"),
696      ValueType::Path => write!(f, "<path>"),
697      ValueType::Thunk => write!(f, "<thunk>"),
698      ValueType::External => write!(f, "<external>"),
699    }
700  }
701}
702
703impl fmt::Debug for Value<'_> {
704  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
705    let value_type = self.value_type();
706    match value_type {
707      ValueType::Int => {
708        if let Ok(val) = self.as_int() {
709          write!(f, "Value::Int({val})")
710        } else {
711          write!(f, "Value::Int(<error>)")
712        }
713      },
714      ValueType::Float => {
715        if let Ok(val) = self.as_float() {
716          write!(f, "Value::Float({val})")
717        } else {
718          write!(f, "Value::Float(<error>)")
719        }
720      },
721      ValueType::Bool => {
722        if let Ok(val) = self.as_bool() {
723          write!(f, "Value::Bool({val})")
724        } else {
725          write!(f, "Value::Bool(<error>)")
726        }
727      },
728      ValueType::String => {
729        if let Ok(val) = self.as_string() {
730          write!(f, "Value::String({val:?})")
731        } else {
732          write!(f, "Value::String(<error>)")
733        }
734      },
735      ValueType::Null => write!(f, "Value::Null"),
736      ValueType::Attrs => write!(f, "Value::Attrs({{ <attrs> }})"),
737      ValueType::List => write!(f, "Value::List([ <list> ])"),
738      ValueType::Function => write!(f, "Value::Function(<function>)"),
739      ValueType::Path => write!(f, "Value::Path(<path>)"),
740      ValueType::Thunk => write!(f, "Value::Thunk(<thunk>)"),
741      ValueType::External => write!(f, "Value::External(<external>)"),
742    }
743  }
744}
745
746#[cfg(test)]
747mod tests {
748  use super::*;
749
750  #[test]
751  #[serial]
752  fn test_context_creation() {
753    let _ctx = Context::new().expect("Failed to create context");
754    // Context should be dropped automatically
755  }
756
757  #[test]
758  #[serial]
759  fn test_eval_state_builder() {
760    let ctx = Arc::new(Context::new().expect("Failed to create context"));
761    let store =
762      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
763    let _state = EvalStateBuilder::new(&store)
764      .expect("Failed to create builder")
765      .build()
766      .expect("Failed to build state");
767    // State should be dropped automatically
768  }
769
770  #[test]
771  #[serial]
772  fn test_simple_evaluation() {
773    let ctx = Arc::new(Context::new().expect("Failed to create context"));
774    let store =
775      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
776    let state = EvalStateBuilder::new(&store)
777      .expect("Failed to create builder")
778      .build()
779      .expect("Failed to build state");
780
781    let result = state
782      .eval_from_string("1 + 2", "<eval>")
783      .expect("Failed to evaluate expression");
784
785    assert_eq!(result.value_type(), ValueType::Int);
786    assert_eq!(result.as_int().expect("Failed to get int value"), 3);
787  }
788
789  #[test]
790  #[serial]
791  fn test_value_types() {
792    let ctx = Arc::new(Context::new().expect("Failed to create context"));
793    let store =
794      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
795    let state = EvalStateBuilder::new(&store)
796      .expect("Failed to create builder")
797      .build()
798      .expect("Failed to build state");
799
800    // Test integer
801    let int_val = state
802      .eval_from_string("42", "<eval>")
803      .expect("Failed to evaluate int");
804    assert_eq!(int_val.value_type(), ValueType::Int);
805    assert_eq!(int_val.as_int().expect("Failed to get int"), 42);
806
807    // Test boolean
808    let bool_val = state
809      .eval_from_string("true", "<eval>")
810      .expect("Failed to evaluate bool");
811    assert_eq!(bool_val.value_type(), ValueType::Bool);
812    assert!(bool_val.as_bool().expect("Failed to get bool"));
813
814    // Test string
815    let str_val = state
816      .eval_from_string("\"hello\"", "<eval>")
817      .expect("Failed to evaluate string");
818    assert_eq!(str_val.value_type(), ValueType::String);
819    assert_eq!(str_val.as_string().expect("Failed to get string"), "hello");
820  }
821
822  #[test]
823  #[serial]
824  fn test_value_formatting() {
825    let ctx = Arc::new(Context::new().expect("Failed to create context"));
826    let store =
827      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
828    let state = EvalStateBuilder::new(&store)
829      .expect("Failed to create builder")
830      .build()
831      .expect("Failed to build state");
832
833    // Test integer formatting
834    let int_val = state
835      .eval_from_string("42", "<eval>")
836      .expect("Failed to evaluate int");
837    assert_eq!(format!("{int_val}"), "42");
838    assert_eq!(format!("{int_val:?}"), "Value::Int(42)");
839    assert_eq!(int_val.to_nix_string().expect("Failed to format"), "42");
840
841    // Test boolean formatting
842    let bool_val = state
843      .eval_from_string("true", "<eval>")
844      .expect("Failed to evaluate bool");
845    assert_eq!(format!("{bool_val}"), "true");
846    assert_eq!(format!("{bool_val:?}"), "Value::Bool(true)");
847    assert_eq!(bool_val.to_nix_string().expect("Failed to format"), "true");
848
849    let false_val = state
850      .eval_from_string("false", "<eval>")
851      .expect("Failed to evaluate bool");
852    assert_eq!(format!("{false_val}"), "false");
853    assert_eq!(
854      false_val.to_nix_string().expect("Failed to format"),
855      "false"
856    );
857
858    // Test string formatting
859    let str_val = state
860      .eval_from_string("\"hello world\"", "<eval>")
861      .expect("Failed to evaluate string");
862    assert_eq!(format!("{str_val}"), "hello world");
863    assert_eq!(format!("{str_val:?}"), "Value::String(\"hello world\")");
864    assert_eq!(
865      str_val.to_nix_string().expect("Failed to format"),
866      "\"hello world\""
867    );
868
869    // Test string with quotes
870    let quoted_str = state
871      .eval_from_string("\"say \\\"hello\\\"\"", "<eval>")
872      .expect("Failed to evaluate quoted string");
873    assert_eq!(format!("{quoted_str}"), "say \"hello\"");
874    assert_eq!(
875      quoted_str.to_nix_string().expect("Failed to format"),
876      "\"say \\\"hello\\\"\""
877    );
878
879    // Test null formatting
880    let null_val = state
881      .eval_from_string("null", "<eval>")
882      .expect("Failed to evaluate null");
883    assert_eq!(format!("{null_val}"), "null");
884    assert_eq!(format!("{null_val:?}"), "Value::Null");
885    assert_eq!(null_val.to_nix_string().expect("Failed to format"), "null");
886
887    // Test collection formatting
888    let attrs_val = state
889      .eval_from_string("{ a = 1; }", "<eval>")
890      .expect("Failed to evaluate attrs");
891    assert_eq!(format!("{attrs_val}"), "{ <attrs> }");
892    assert_eq!(format!("{attrs_val:?}"), "Value::Attrs({ <attrs> })");
893
894    let list_val = state
895      .eval_from_string("[ 1 2 3 ]", "<eval>")
896      .expect("Failed to evaluate list");
897    assert_eq!(format!("{list_val}"), "[ <list> ]");
898    assert_eq!(format!("{list_val:?}"), "Value::List([ <list> ])");
899  }
900}