feat: EvalState.require_attrs_names

(cherry picked from commit 1f9fdcb3805ecf74e3c0451c11bdfae96f6f997b)
This commit is contained in:
Robert Hensing 2024-04-09 16:20:18 +02:00
parent 024ef333cb
commit 49576f82e4

View file

@ -9,6 +9,7 @@ use nix_util::context::Context;
use nix_util::result_string_init;
use nix_util::string_return::{callback_get_result_string, callback_get_result_string_data};
use std::ffi::CString;
use std::os::raw::c_uint;
use std::ptr::null_mut;
use std::ptr::NonNull;
@ -123,6 +124,34 @@ impl EvalState {
let i = unsafe { raw::get_int(self.context.ptr(), v.raw_ptr()) };
Ok(i)
}
/// Evaluate, and require that the value is an attrset.
/// Returns a list of the keys in the attrset.
pub fn require_attrs_names(&self, v: &Value) -> Result<Vec<String>> {
let t = self.value_type(v)?;
if t != ValueType::AttrSet {
bail!("expected an attrset, but got a {:?}", t);
}
let n = unsafe { raw::get_attrs_size(self.context.ptr(), v.raw_ptr()) as usize };
self.context.check_err()?;
let mut attrs = Vec::with_capacity(n);
unsafe {
for i in 0..n {
let cstr_ptr: *const i8 = raw::get_attr_name_byidx(
self.context.ptr(),
v.raw_ptr(),
self.raw_ptr(),
i as c_uint,
);
self.context.check_err()?;
let cstr = std::ffi::CStr::from_ptr(cstr_ptr);
let s = cstr.to_str().map_err(|e| {
anyhow::format_err!("Nix attrset key is not valid UTF-8: {}", e)
})?;
attrs.insert(i, s.to_owned());
}
}
Ok(attrs)
}
/// Create a new value containing the passed string.
/// Returns a string value without any string context.
@ -366,6 +395,53 @@ mod tests {
.unwrap();
}
#[test]
fn eval_state_value_attrs_names_empty() {
gc_registering_current_thread(|| {
let store = Store::open("auto").unwrap();
let es = EvalState::new(store).unwrap();
let v = es.eval_from_string("{ }", "<test>").unwrap();
es.force(&v).unwrap();
let t = es.value_type(&v).unwrap();
assert!(t == ValueType::AttrSet);
let attrs = es.require_attrs_names(&v).unwrap();
assert_eq!(attrs.len(), 0);
})
.unwrap()
}
#[test]
fn eval_state_require_attrs_names_bad_type() {
gc_registering_current_thread(|| {
let store = Store::open("auto").unwrap();
let es = EvalState::new(store).unwrap();
let v = es.eval_from_string("1", "<test>").unwrap();
es.force(&v).unwrap();
let r = es.require_attrs_names(&v);
assert!(r.is_err());
assert_eq!(
r.unwrap_err().to_string(),
"expected an attrset, but got a Int"
);
})
.unwrap()
}
#[test]
fn eval_state_value_attrs_names_example() {
gc_registering_current_thread(|| {
let store = Store::open("auto").unwrap();
let es = EvalState::new(store).unwrap();
let expr = r#"{ a = throw "nope a"; b = throw "nope b"; }"#;
let v = es.eval_from_string(expr, "<test>").unwrap();
let attrs = es.require_attrs_names(&v).unwrap();
assert_eq!(attrs.len(), 2);
assert_eq!(attrs[0], "a");
assert_eq!(attrs[1], "b");
})
.unwrap();
}
#[test]
fn eval_state_value_string() {
gc_registering_current_thread(|| {