1use std::{ffi::CString, ptr::NonNull};
2
3use crate::{Error, Result, Value};
4
5impl Value<'_> {
6 pub fn get_attr(&self, key: &str) -> Result<Value<'_>> {
15 if self.value_type() != crate::ValueType::Attrs {
16 return Err(Error::InvalidType {
17 expected: "attrs",
18 actual: self.value_type().to_string(),
19 });
20 }
21
22 let key_c = CString::new(key)?;
23
24 let attr_ptr = unsafe {
28 crate::sys::nix_get_attr_byname(
29 self.state.context.as_ptr(),
30 self.inner.as_ptr(),
31 self.state.as_ptr(),
32 key_c.as_ptr(),
33 )
34 };
35
36 match NonNull::new(attr_ptr) {
37 Some(inner) => {
38 Ok(Value {
39 inner,
40 state: self.state,
41 })
42 },
43 None => {
44 let ptr = unsafe {
47 crate::sys::nix_err_msg(
48 std::ptr::null_mut(),
49 self.state.context.as_ptr(),
50 std::ptr::null_mut(),
51 )
52 };
53 if ptr.is_null() {
54 Err(Error::KeyNotFound(key.to_string()))
55 } else {
56 let msg = unsafe {
57 std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned()
58 };
59 if msg.is_empty() {
60 Err(Error::KeyNotFound(key.to_string()))
61 } else {
62 Err(Error::Unknown(msg))
63 }
64 }
65 },
66 }
67 }
68
69 pub fn attr_keys(&self) -> Result<Vec<String>> {
77 if self.value_type() != crate::ValueType::Attrs {
78 return Err(Error::InvalidType {
79 expected: "attrs",
80 actual: self.value_type().to_string(),
81 });
82 }
83
84 let count = unsafe {
86 crate::sys::nix_get_attrs_size(
87 self.state.context.as_ptr(),
88 self.inner.as_ptr(),
89 )
90 };
91
92 let mut keys = Vec::with_capacity(count as usize);
93
94 for i in 0..count {
95 let name_ptr = unsafe {
100 crate::sys::nix_get_attr_name_byidx(
101 self.state.context.as_ptr(),
102 self.inner.as_ptr(),
103 self.state.as_ptr(),
104 i,
105 )
106 };
107
108 if name_ptr.is_null() {
109 continue;
110 }
111
112 let name = unsafe { std::ffi::CStr::from_ptr(name_ptr) }
114 .to_str()
115 .map_err(|_| {
116 Error::Unknown("Attribute name was not valid UTF-8".to_string())
117 })?
118 .to_owned();
119 keys.push(name);
120 }
121
122 Ok(keys)
123 }
124
125 pub fn has_attr(&self, key: &str) -> Result<bool> {
133 if self.value_type() != crate::ValueType::Attrs {
134 return Err(Error::InvalidType {
135 expected: "attrs",
136 actual: self.value_type().to_string(),
137 });
138 }
139
140 let key_c = CString::new(key)?;
141
142 let result = unsafe {
144 crate::sys::nix_has_attr_byname(
145 self.state.context.as_ptr(),
146 self.inner.as_ptr(),
147 self.state.as_ptr(),
148 key_c.as_ptr(),
149 )
150 };
151
152 Ok(result)
153 }
154
155 pub fn attrs(&self) -> Result<AttrIterator<'_>> {
165 if self.value_type() != crate::ValueType::Attrs {
166 return Err(Error::InvalidType {
167 expected: "attrs",
168 actual: self.value_type().to_string(),
169 });
170 }
171
172 let count = unsafe {
174 crate::sys::nix_get_attrs_size(
175 self.state.context.as_ptr(),
176 self.inner.as_ptr(),
177 )
178 };
179
180 Ok(AttrIterator {
181 value: self,
182 index: 0,
183 count: count as usize,
184 })
185 }
186}
187
188pub struct AttrIterator<'a> {
194 value: &'a Value<'a>,
195 index: usize,
196 count: usize,
197}
198
199impl<'a> Iterator for AttrIterator<'a> {
200 type Item = Result<(String, Value<'a>)>;
202
203 fn next(&mut self) -> Option<Self::Item> {
204 if self.index >= self.count {
205 return None;
206 }
207
208 let idx = self.index;
209 self.index += 1;
210
211 let mut name_ptr: *const std::os::raw::c_char = std::ptr::null();
216 let attr_ptr = unsafe {
217 crate::sys::nix_get_attr_byidx_lazy(
218 self.value.state.context.as_ptr(),
219 self.value.inner.as_ptr(),
220 self.value.state.as_ptr(),
221 idx as std::os::raw::c_uint,
222 &mut name_ptr,
223 )
224 };
225
226 if attr_ptr.is_null() {
227 return Some(Err(Error::NullPointer));
228 }
229
230 if name_ptr.is_null() {
231 unsafe {
233 crate::sys::nix_value_decref(
234 self.value.state.context.as_ptr(),
235 attr_ptr,
236 );
237 }
238 return Some(Err(Error::NullPointer));
239 }
240
241 let name = match unsafe { std::ffi::CStr::from_ptr(name_ptr) }.to_str() {
243 Ok(s) => s.to_owned(),
244 Err(_) => {
245 unsafe {
246 crate::sys::nix_value_decref(
247 self.value.state.context.as_ptr(),
248 attr_ptr,
249 );
250 }
251 return Some(Err(Error::Unknown(
252 "Attribute name was not valid UTF-8".to_string(),
253 )));
254 },
255 };
256
257 let inner = unsafe { NonNull::new_unchecked(attr_ptr) };
259
260 let value = Value {
261 inner,
262 state: self.value.state,
263 };
264
265 Some(Ok((name, value)))
266 }
267
268 fn size_hint(&self) -> (usize, Option<usize>) {
269 let remaining = self.count - self.index;
270 (remaining, Some(remaining))
271 }
272}
273
274impl ExactSizeIterator for AttrIterator<'_> {}
275
276#[cfg(test)]
277mod tests {
278 use std::sync::Arc;
279
280 use serial_test::serial;
281
282 use crate::{Context, EvalStateBuilder, Store};
283
284 fn setup() -> EvalStateBuilder {
285 let ctx = Arc::new(Context::new().expect("Failed to create context"));
286 let store =
287 Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
288 EvalStateBuilder::new(&store).expect("Failed to create builder")
289 }
290
291 #[test]
292 #[serial]
293 fn test_get_attr() {
294 let state = setup().build().expect("Failed to build state");
295 let attrs = state
296 .eval_from_string("{ foo = 42; bar = \"hello\"; }", "<eval>")
297 .expect("Failed to evaluate attrs");
298
299 let foo = attrs.get_attr("foo").expect("Failed to get foo");
300 assert_eq!(foo.as_int().expect("Failed to get int"), 42);
301
302 let bar = attrs.get_attr("bar").expect("Failed to get bar");
303 assert_eq!(bar.as_string().expect("Failed to get string"), "hello");
304 }
305
306 #[test]
307 #[serial]
308 fn test_get_attr_missing() {
309 let state = setup().build().expect("Failed to build state");
310 let attrs = state
311 .eval_from_string("{ foo = 42; }", "<eval>")
312 .expect("Failed to evaluate attrs");
313
314 let result = attrs.get_attr("missing");
315 assert!(result.is_err());
316 }
317
318 #[test]
319 #[serial]
320 fn test_attr_keys() {
321 let state = setup().build().expect("Failed to build state");
322 let attrs = state
323 .eval_from_string("{ foo = 1; bar = 2; baz = 3; }", "<eval>")
324 .expect("Failed to evaluate attrs");
325
326 let keys = attrs.attr_keys().expect("Failed to get keys");
327 assert_eq!(keys.len(), 3);
328 assert!(keys.contains(&"foo".to_string()));
329 assert!(keys.contains(&"bar".to_string()));
330 assert!(keys.contains(&"baz".to_string()));
331 }
332
333 #[test]
334 #[serial]
335 fn test_has_attr() {
336 let state = setup().build().expect("Failed to build state");
337 let attrs = state
338 .eval_from_string("{ foo = 42; }", "<eval>")
339 .expect("Failed to evaluate attrs");
340
341 assert!(attrs.has_attr("foo").expect("Failed to check attr"));
342 assert!(!attrs.has_attr("bar").expect("Failed to check missing attr"));
343 }
344
345 #[test]
346 #[serial]
347 fn test_attr_iterator() {
348 let state = setup().build().expect("Failed to build state");
349 let attrs = state
350 .eval_from_string("{ a = 1; b = 2; c = 3; }", "<eval>")
351 .expect("Failed to evaluate attrs");
352
353 let iter = attrs.attrs().expect("Failed to create iterator");
354 let collected: Vec<_> = iter.collect();
355
356 assert_eq!(collected.len(), 3);
357 }
358
359 #[test]
360 #[serial]
361 fn test_empty_attrs() {
362 let state = setup().build().expect("Failed to build state");
363 let attrs = state
364 .eval_from_string("{}", "<eval>")
365 .expect("Failed to evaluate empty attrs");
366
367 let keys = attrs.attr_keys().expect("Failed to get keys");
368 assert!(keys.is_empty());
369
370 let has = attrs.has_attr("foo").expect("Failed to check attr");
371 assert!(!has);
372 }
373}