Skip to main content

nix_bindings/
value_ops.rs

1//! Shared value-access trait for crate-level [`Value`](crate::Value) and the
2//! callback-scoped wrappers in [`primop`](crate::primop).
3//!
4//! Bring [`NixValueOps`] into scope to use the read accessors generically:
5//!
6//! ```ignore
7//! use nix_bindings::NixValueOps;
8//!
9//! fn sum<V: NixValueOps>(values: &[V]) -> nix_bindings::Result<i64> {
10//!   values.iter().map(|v| v.as_int()).sum()
11//! }
12//! ```
13
14use std::ffi::CStr;
15
16use crate::{Error, Result, ValueType, check_err, sys};
17
18pub(crate) mod sealed {
19  use super::sys;
20
21  pub trait NixValueRaw {
22    fn raw_ctx(&self) -> *mut sys::nix_c_context;
23    fn raw_state(&self) -> *mut sys::EvalState;
24    fn raw_inner(&self) -> *mut sys::nix_value;
25  }
26}
27
28// Re-exported under the module namespace so other crate modules can
29// `impl crate::value_ops::NixValueRaw for Foo` to opt their type into
30// [`NixValueOps`]. The trait stays sealed by the private `sealed`
31// module: downstream crates cannot name it.
32pub(crate) use sealed::NixValueRaw;
33
34/// Read access to a Nix value.
35///
36/// Implemented by [`Value`](crate::Value),
37/// [`primop::PrimOpArg`](crate::primop::PrimOpArg), and
38/// [`primop::PrimOpValue`](crate::primop::PrimOpValue). All accessors force
39/// the value first, mirroring `nix-instantiate`-style semantics: a lazy
40/// attribute that resolves to an int is reported as an int, not a thunk.
41pub trait NixValueOps: sealed::NixValueRaw {
42  /// Return the [`ValueType`] of this value.
43  ///
44  /// Does **not** force; an unforced thunk reports as [`ValueType::Thunk`].
45  /// Use [`force`](Self::force) (or any `as_*` accessor) to resolve first.
46  fn value_type(&self) -> ValueType {
47    // SAFETY: ctx and inner are valid for the wrapper's lifetime.
48    let c_type = unsafe { sys::nix_get_type(self.raw_ctx(), self.raw_inner()) };
49    ValueType::from_c(c_type)
50  }
51
52  /// Force evaluation (resolves thunks).
53  ///
54  /// # Errors
55  ///
56  /// Returns an error if evaluation fails.
57  fn force(&self) -> Result<()> {
58    // SAFETY: ctx, state, and inner are valid for the wrapper's lifetime.
59    unsafe {
60      check_err(
61        self.raw_ctx(),
62        sys::nix_value_force(
63          self.raw_ctx(),
64          self.raw_state(),
65          self.raw_inner(),
66        ),
67      )
68    }
69  }
70
71  /// Extract as an integer.  Forces the value first.
72  ///
73  /// # Errors
74  ///
75  /// Returns an error if forcing fails or the resolved value is not an
76  /// integer.
77  fn as_int(&self) -> Result<i64> {
78    self.force()?;
79    if self.value_type() != ValueType::Int {
80      return Err(Error::InvalidType {
81        expected: "int",
82        actual:   self.value_type().to_string(),
83      });
84    }
85    // SAFETY: type checked.
86    Ok(unsafe { sys::nix_get_int(self.raw_ctx(), self.raw_inner()) })
87  }
88
89  /// Extract as a float.  Forces the value first.
90  ///
91  /// # Errors
92  ///
93  /// Returns an error if forcing fails or the resolved value is not a
94  /// float.
95  fn as_float(&self) -> Result<f64> {
96    self.force()?;
97    if self.value_type() != ValueType::Float {
98      return Err(Error::InvalidType {
99        expected: "float",
100        actual:   self.value_type().to_string(),
101      });
102    }
103    // SAFETY: type checked.
104    Ok(unsafe { sys::nix_get_float(self.raw_ctx(), self.raw_inner()) })
105  }
106
107  /// Extract as a boolean.  Forces the value first.
108  ///
109  /// # Errors
110  ///
111  /// Returns an error if forcing fails or the resolved value is not a
112  /// boolean.
113  fn as_bool(&self) -> Result<bool> {
114    self.force()?;
115    if self.value_type() != ValueType::Bool {
116      return Err(Error::InvalidType {
117        expected: "bool",
118        actual:   self.value_type().to_string(),
119      });
120    }
121    // SAFETY: type checked.
122    Ok(unsafe { sys::nix_get_bool(self.raw_ctx(), self.raw_inner()) })
123  }
124
125  /// Extract as a UTF-8 string, realising any string context.  Forces
126  /// the value first.
127  ///
128  /// # Errors
129  ///
130  /// Returns an error if forcing fails, the resolved value is not a
131  /// string, or the string contains invalid UTF-8.
132  fn as_string(&self) -> Result<String> {
133    self.force()?;
134    if self.value_type() != ValueType::String {
135      return Err(Error::InvalidType {
136        expected: "string",
137        actual:   self.value_type().to_string(),
138      });
139    }
140
141    // SAFETY: type checked.
142    let realised_str = unsafe {
143      sys::nix_string_realise(
144        self.raw_ctx(),
145        self.raw_state(),
146        self.raw_inner(),
147        false,
148      )
149    };
150
151    if realised_str.is_null() {
152      return Err(Error::NullPointer);
153    }
154
155    let buffer_start =
156      unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
157    let buffer_size =
158      unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
159
160    if buffer_start.is_null() {
161      unsafe { sys::nix_realised_string_free(realised_str) };
162      return Err(Error::NullPointer);
163    }
164
165    let bytes = unsafe {
166      std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size)
167    };
168    let s = std::str::from_utf8(bytes)
169      .map_err(|_| Error::Unknown("Invalid UTF-8 in string".into()))?
170      .to_owned();
171
172    unsafe { sys::nix_realised_string_free(realised_str) };
173    Ok(s)
174  }
175
176  /// Extract as a filesystem-path string.  Forces the value first.
177  ///
178  /// # Errors
179  ///
180  /// Returns an error if forcing fails, the resolved value is not a
181  /// path, or the path contains invalid UTF-8.
182  fn as_path(&self) -> Result<String> {
183    self.force()?;
184    if self.value_type() != ValueType::Path {
185      return Err(Error::InvalidType {
186        expected: "path",
187        actual:   self.value_type().to_string(),
188      });
189    }
190    // SAFETY: type checked; pointer outlives the copy.
191    let raw =
192      unsafe { sys::nix_get_path_string(self.raw_ctx(), self.raw_inner()) };
193    if raw.is_null() {
194      return Err(Error::NullPointer);
195    }
196    let cstr = unsafe { CStr::from_ptr(raw) };
197    cstr
198      .to_str()
199      .map(str::to_owned)
200      .map_err(|_| Error::Unknown("Invalid UTF-8 in path".into()))
201  }
202}
203
204impl<T: sealed::NixValueRaw> NixValueOps for T {}