Skip to main content

illumos_nvpair/
lib.rs

1#![allow(non_upper_case_globals)]
2
3//! Idiomatic Rust representation of illumos nvlists.
4//!
5//! This crate converts raw `nvlist_t` pointers (from libnvpair) into
6//! pure Rust types with no raw pointers or FFI in the public API.
7//!
8//! # Example
9//!
10//! ```ignore
11//! use illumos_nvpair::{NvList, NvValue};
12//!
13//! // Given a raw nvlist pointer from some illumos API:
14//! let nvl = unsafe { NvList::from_raw(raw_ptr) }?;
15//!
16//! if let Some(NvValue::String(s)) = nvl.lookup("name") {
17//!     println!("name = {s}");
18//! }
19//! ```
20
21use std::ffi::CStr;
22use std::fmt;
23use std::os::raw::c_char;
24
25use illumos_nvpair_sys::{
26    data_type_t, data_type_t_DATA_TYPE_BOOLEAN, data_type_t_DATA_TYPE_BOOLEAN_ARRAY,
27    data_type_t_DATA_TYPE_BOOLEAN_VALUE, data_type_t_DATA_TYPE_BYTE,
28    data_type_t_DATA_TYPE_BYTE_ARRAY, data_type_t_DATA_TYPE_DOUBLE, data_type_t_DATA_TYPE_HRTIME,
29    data_type_t_DATA_TYPE_INT8, data_type_t_DATA_TYPE_INT8_ARRAY, data_type_t_DATA_TYPE_INT16,
30    data_type_t_DATA_TYPE_INT16_ARRAY, data_type_t_DATA_TYPE_INT32,
31    data_type_t_DATA_TYPE_INT32_ARRAY, data_type_t_DATA_TYPE_INT64,
32    data_type_t_DATA_TYPE_INT64_ARRAY, data_type_t_DATA_TYPE_NVLIST,
33    data_type_t_DATA_TYPE_NVLIST_ARRAY, data_type_t_DATA_TYPE_STRING,
34    data_type_t_DATA_TYPE_STRING_ARRAY, data_type_t_DATA_TYPE_UINT8,
35    data_type_t_DATA_TYPE_UINT8_ARRAY, data_type_t_DATA_TYPE_UINT16,
36    data_type_t_DATA_TYPE_UINT16_ARRAY, data_type_t_DATA_TYPE_UINT32,
37    data_type_t_DATA_TYPE_UINT32_ARRAY, data_type_t_DATA_TYPE_UINT64,
38    data_type_t_DATA_TYPE_UINT64_ARRAY, nvlist_next_nvpair, nvlist_t, nvpair_name, nvpair_t,
39    nvpair_type, nvpair_value_boolean_array, nvpair_value_boolean_value, nvpair_value_byte,
40    nvpair_value_byte_array, nvpair_value_double, nvpair_value_hrtime, nvpair_value_int8,
41    nvpair_value_int8_array, nvpair_value_int16, nvpair_value_int16_array, nvpair_value_int32,
42    nvpair_value_int32_array, nvpair_value_int64, nvpair_value_int64_array, nvpair_value_nvlist,
43    nvpair_value_nvlist_array, nvpair_value_string, nvpair_value_string_array, nvpair_value_uint8,
44    nvpair_value_uint8_array, nvpair_value_uint16, nvpair_value_uint16_array, nvpair_value_uint32,
45    nvpair_value_uint32_array, nvpair_value_uint64, nvpair_value_uint64_array, uint_t,
46};
47
48/// Error returned when reading an nvpair value fails.
49#[derive(Debug, Clone, PartialEq)]
50pub enum NvError {
51    /// A `nvpair_value_*` C function returned a non-zero error code.
52    ValueReadFailed {
53        pair_name: String,
54        type_code: data_type_t,
55        errno: i32,
56    },
57    /// A C function returned a null pointer where a valid one was required.
58    NullPointer {
59        pair_name: String,
60        type_code: data_type_t,
61    },
62    /// `nvpair_name` returned a null pointer for a pair.
63    NullName,
64}
65
66impl fmt::Display for NvError {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self {
69            NvError::ValueReadFailed {
70                pair_name,
71                type_code,
72                errno,
73            } => {
74                write!(
75                    f,
76                    "nvpair_value failed for pair {:?} (type {type_code}): errno {errno}",
77                    pair_name,
78                )
79            }
80            NvError::NullPointer {
81                pair_name,
82                type_code,
83            } => {
84                write!(
85                    f,
86                    "nvpair_value returned null for pair {:?} (type {type_code})",
87                    pair_name,
88                )
89            }
90            NvError::NullName => {
91                write!(f, "nvpair_name returned null")
92            }
93        }
94    }
95}
96
97impl std::error::Error for NvError {}
98
99/// An ordered list of name-value pairs, converted from an illumos nvlist.
100#[derive(Debug, Clone, PartialEq)]
101pub struct NvList {
102    pairs: Vec<(String, NvValue)>,
103}
104
105impl NvList {
106    /// Convert a raw `nvlist_t *` into an owned [`NvList`].
107    ///
108    /// Pair names and string values are converted from C strings using
109    /// lossy UTF-8 conversion: any bytes that are not valid UTF-8 are
110    /// replaced with U+FFFD (`\u{FFFD}`). This means a `lookup()` call
111    /// will not match the original name if it contained non-UTF-8 bytes.
112    ///
113    /// This deep-copies the nvlist contents and does not take ownership
114    /// of the pointer. If you instead want a RAII handle that owns and
115    /// frees the underlying C nvlist, see [`OwnedNvList`].
116    ///
117    /// # Safety
118    ///
119    /// `nvl` must be a valid, non-null pointer to an nvlist. The nvlist is
120    /// borrowed - the caller retains ownership and is responsible for
121    /// freeing it.
122    pub unsafe fn from_raw(nvl: *mut nvlist_t) -> Result<NvList, NvError> {
123        let mut pairs = Vec::new();
124        let mut nvp: *mut nvpair_t = std::ptr::null_mut();
125
126        loop {
127            nvp = unsafe { nvlist_next_nvpair(nvl, nvp) };
128            if nvp.is_null() {
129                break;
130            }
131
132            let name_ptr = unsafe { nvpair_name(nvp) };
133            if name_ptr.is_null() {
134                return Err(NvError::NullName);
135            }
136            let name = unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() };
137            let dtype = unsafe { nvpair_type(nvp) };
138            let value = unsafe { read_pair_value(nvp, &name, dtype)? };
139            pairs.push((name, value));
140        }
141
142        Ok(NvList { pairs })
143    }
144
145    /// Look up a value by name.
146    pub fn lookup(&self, name: &str) -> Option<&NvValue> {
147        self.pairs.iter().find(|(n, _)| n == name).map(|(_, v)| v)
148    }
149
150    /// Returns an iterator over the name-value pairs.
151    pub fn iter(&self) -> impl Iterator<Item = (&str, &NvValue)> {
152        self.pairs.iter().map(|(n, v)| (n.as_str(), v))
153    }
154
155    /// Returns the number of pairs.
156    pub fn len(&self) -> usize {
157        self.pairs.len()
158    }
159
160    /// Returns `true` if the list contains no pairs.
161    pub fn is_empty(&self) -> bool {
162        self.pairs.is_empty()
163    }
164}
165
166impl IntoIterator for NvList {
167    type Item = (String, NvValue);
168    type IntoIter = std::vec::IntoIter<(String, NvValue)>;
169
170    fn into_iter(self) -> Self::IntoIter {
171        self.pairs.into_iter()
172    }
173}
174
175impl<'a> IntoIterator for &'a NvList {
176    type Item = (&'a str, &'a NvValue);
177    type IntoIter = std::iter::Map<
178        std::slice::Iter<'a, (String, NvValue)>,
179        fn(&'a (String, NvValue)) -> (&'a str, &'a NvValue),
180    >;
181
182    fn into_iter(self) -> Self::IntoIter {
183        self.pairs.iter().map(|(n, v)| (n.as_str(), v))
184    }
185}
186
187/// Owning RAII handle to a live `nvlist_t` allocated by libnvpair (or by any
188/// illumos API that returns an `nvlist_t *` with caller-frees-it semantics).
189///
190/// Complements [`NvList`], which is a deep-copied snapshot for inspection.
191/// Use `OwnedNvList` when you need to:
192///
193/// - own a live nvlist with RAII cleanup, or
194/// - pass the raw pointer back into a C API that takes `nvlist_t *`.
195///
196/// On drop, the underlying nvlist is freed via `nvlist_free`.
197#[derive(Debug)]
198pub struct OwnedNvList {
199    raw: *mut nvlist_t,
200}
201
202impl OwnedNvList {
203    /// Take ownership of a raw `nvlist_t *`.
204    ///
205    /// # Safety
206    ///
207    /// - `raw` must be non-null and point to a valid nvlist allocated such
208    ///   that it can be freed with `nvlist_free`.
209    /// - Ownership is transferred to the returned `OwnedNvList`. The caller
210    ///   must not free the pointer or wrap it in another `OwnedNvList`.
211    pub unsafe fn from_raw(raw: *mut nvlist_t) -> Self {
212        debug_assert!(!raw.is_null(), "OwnedNvList::from_raw called with null");
213        Self { raw }
214    }
215
216    /// Borrow the underlying raw pointer.
217    ///
218    /// Valid until `self` is dropped. The caller must not free it.
219    pub fn as_raw(&self) -> *mut nvlist_t {
220        self.raw
221    }
222
223    /// Relinquish ownership and return the raw pointer.
224    ///
225    /// The caller is now responsible for eventually freeing the nvlist,
226    /// typically by passing it to a C API that takes ownership or by
227    /// re-wrapping it via [`OwnedNvList::from_raw`].
228    pub fn into_raw(self) -> *mut nvlist_t {
229        let raw = self.raw;
230        std::mem::forget(self);
231        raw
232    }
233
234    /// Deep-copy the nvlist contents into a pure-Rust [`NvList`] for inspection.
235    pub fn inspect(&self) -> Result<NvList, NvError> {
236        unsafe { NvList::from_raw(self.raw) }
237    }
238}
239
240impl Drop for OwnedNvList {
241    fn drop(&mut self) {
242        unsafe { illumos_nvpair_sys::nvlist_free(self.raw) };
243    }
244}
245
246/// A value from an nvlist pair.
247#[derive(Debug, Clone, PartialEq)]
248pub enum NvValue {
249    /// A valueless boolean (presence indicates true).
250    Boolean,
251    /// A boolean with an explicit value.
252    BooleanValue(bool),
253    Byte(u8),
254    Int8(i8),
255    UInt8(u8),
256    Int16(i16),
257    UInt16(u16),
258    Int32(i32),
259    UInt32(u32),
260    Int64(i64),
261    UInt64(u64),
262    Double(f64),
263    String(String),
264    /// High-resolution time in nanoseconds.
265    Hrtime(i64),
266    NvList(NvList),
267    BooleanArray(Vec<bool>),
268    ByteArray(Vec<u8>),
269    Int8Array(Vec<i8>),
270    UInt8Array(Vec<u8>),
271    Int16Array(Vec<i16>),
272    UInt16Array(Vec<u16>),
273    Int32Array(Vec<i32>),
274    UInt32Array(Vec<u32>),
275    Int64Array(Vec<i64>),
276    UInt64Array(Vec<u64>),
277    StringArray(Vec<String>),
278    NvListArray(Vec<NvList>),
279    /// A type not recognized by this crate.
280    Unknown {
281        type_code: data_type_t,
282    },
283}
284
285/// Check a return code from a `nvpair_value_*` call, converting
286/// non-zero into an `NvError`.
287fn check_rc(rc: i32, pair_name: &str, type_code: data_type_t) -> Result<(), NvError> {
288    if rc != 0 {
289        Err(NvError::ValueReadFailed {
290            pair_name: pair_name.to_owned(),
291            type_code,
292            errno: rc,
293        })
294    } else {
295        Ok(())
296    }
297}
298
299/// Safely convert a C array pointer + count into a `Vec<T>`.
300/// Returns an empty `Vec` when `n == 0` (avoiding `from_raw_parts`
301/// on a potentially null pointer). Returns `NvError::NullPointer`
302/// if `n > 0` but the pointer is null.
303///
304/// # Safety
305///
306/// When `n > 0`, `p` must point to `n` valid, aligned, initialised
307/// elements of `T`.
308unsafe fn array_to_vec<T: Clone>(
309    p: *const T,
310    n: uint_t,
311    pair_name: &str,
312    type_code: data_type_t,
313) -> Result<Vec<T>, NvError> {
314    unsafe {
315        let len = n as usize;
316        if len == 0 {
317            return Ok(Vec::new());
318        }
319        if p.is_null() {
320            return Err(NvError::NullPointer {
321                pair_name: pair_name.to_owned(),
322                type_code,
323            });
324        }
325        Ok(std::slice::from_raw_parts(p, len).to_vec())
326    }
327}
328
329unsafe fn read_pair_value(
330    nvp: *mut nvpair_t,
331    pair_name: &str,
332    dtype: data_type_t,
333) -> Result<NvValue, NvError> {
334    unsafe {
335        match dtype {
336            data_type_t_DATA_TYPE_BOOLEAN => Ok(NvValue::Boolean),
337            data_type_t_DATA_TYPE_BOOLEAN_VALUE => {
338                let mut v: illumos_nvpair_sys::boolean_t = 0;
339                check_rc(nvpair_value_boolean_value(nvp, &mut v), pair_name, dtype)?;
340                Ok(NvValue::BooleanValue(v != 0))
341            }
342            data_type_t_DATA_TYPE_BYTE => {
343                let mut v: illumos_nvpair_sys::uchar_t = 0;
344                check_rc(nvpair_value_byte(nvp, &mut v), pair_name, dtype)?;
345                Ok(NvValue::Byte(v))
346            }
347            data_type_t_DATA_TYPE_INT8 => {
348                let mut v: i8 = 0;
349                check_rc(nvpair_value_int8(nvp, &mut v), pair_name, dtype)?;
350                Ok(NvValue::Int8(v))
351            }
352            data_type_t_DATA_TYPE_UINT8 => {
353                let mut v: u8 = 0;
354                check_rc(nvpair_value_uint8(nvp, &mut v), pair_name, dtype)?;
355                Ok(NvValue::UInt8(v))
356            }
357            data_type_t_DATA_TYPE_INT16 => {
358                let mut v: i16 = 0;
359                check_rc(nvpair_value_int16(nvp, &mut v), pair_name, dtype)?;
360                Ok(NvValue::Int16(v))
361            }
362            data_type_t_DATA_TYPE_UINT16 => {
363                let mut v: u16 = 0;
364                check_rc(nvpair_value_uint16(nvp, &mut v), pair_name, dtype)?;
365                Ok(NvValue::UInt16(v))
366            }
367            data_type_t_DATA_TYPE_INT32 => {
368                let mut v: i32 = 0;
369                check_rc(nvpair_value_int32(nvp, &mut v), pair_name, dtype)?;
370                Ok(NvValue::Int32(v))
371            }
372            data_type_t_DATA_TYPE_UINT32 => {
373                let mut v: u32 = 0;
374                check_rc(nvpair_value_uint32(nvp, &mut v), pair_name, dtype)?;
375                Ok(NvValue::UInt32(v))
376            }
377            data_type_t_DATA_TYPE_INT64 => {
378                let mut v: i64 = 0;
379                check_rc(nvpair_value_int64(nvp, &mut v), pair_name, dtype)?;
380                Ok(NvValue::Int64(v))
381            }
382            data_type_t_DATA_TYPE_UINT64 => {
383                let mut v: u64 = 0;
384                check_rc(nvpair_value_uint64(nvp, &mut v), pair_name, dtype)?;
385                Ok(NvValue::UInt64(v))
386            }
387            data_type_t_DATA_TYPE_DOUBLE => {
388                let mut v: f64 = 0.0;
389                check_rc(nvpair_value_double(nvp, &mut v), pair_name, dtype)?;
390                Ok(NvValue::Double(v))
391            }
392            data_type_t_DATA_TYPE_STRING => {
393                let mut p: *mut c_char = std::ptr::null_mut();
394                check_rc(nvpair_value_string(nvp, &mut p), pair_name, dtype)?;
395                if p.is_null() {
396                    return Err(NvError::NullPointer {
397                        pair_name: pair_name.to_owned(),
398                        type_code: dtype,
399                    });
400                }
401                let s = CStr::from_ptr(p).to_string_lossy().into_owned();
402                Ok(NvValue::String(s))
403            }
404            data_type_t_DATA_TYPE_HRTIME => {
405                let mut v: illumos_nvpair_sys::hrtime_t = 0;
406                check_rc(nvpair_value_hrtime(nvp, &mut v), pair_name, dtype)?;
407                Ok(NvValue::Hrtime(v))
408            }
409            data_type_t_DATA_TYPE_NVLIST => {
410                let mut p: *mut nvlist_t = std::ptr::null_mut();
411                check_rc(nvpair_value_nvlist(nvp, &mut p), pair_name, dtype)?;
412                if p.is_null() {
413                    return Err(NvError::NullPointer {
414                        pair_name: pair_name.to_owned(),
415                        type_code: dtype,
416                    });
417                }
418                Ok(NvValue::NvList(NvList::from_raw(p)?))
419            }
420            data_type_t_DATA_TYPE_BOOLEAN_ARRAY => {
421                let mut p: *mut illumos_nvpair_sys::boolean_t = std::ptr::null_mut();
422                let mut n: uint_t = 0;
423                check_rc(
424                    nvpair_value_boolean_array(nvp, &mut p, &mut n),
425                    pair_name,
426                    dtype,
427                )?;
428                let raw = array_to_vec(p, n, pair_name, dtype)?;
429                Ok(NvValue::BooleanArray(raw.iter().map(|&v| v != 0).collect()))
430            }
431            data_type_t_DATA_TYPE_BYTE_ARRAY => {
432                let mut p: *mut illumos_nvpair_sys::uchar_t = std::ptr::null_mut();
433                let mut n: uint_t = 0;
434                check_rc(
435                    nvpair_value_byte_array(nvp, &mut p, &mut n),
436                    pair_name,
437                    dtype,
438                )?;
439                Ok(NvValue::ByteArray(array_to_vec(p, n, pair_name, dtype)?))
440            }
441            data_type_t_DATA_TYPE_INT8_ARRAY => {
442                let mut p: *mut i8 = std::ptr::null_mut();
443                let mut n: uint_t = 0;
444                check_rc(
445                    nvpair_value_int8_array(nvp, &mut p, &mut n),
446                    pair_name,
447                    dtype,
448                )?;
449                Ok(NvValue::Int8Array(array_to_vec(p, n, pair_name, dtype)?))
450            }
451            data_type_t_DATA_TYPE_UINT8_ARRAY => {
452                let mut p: *mut u8 = std::ptr::null_mut();
453                let mut n: uint_t = 0;
454                check_rc(
455                    nvpair_value_uint8_array(nvp, &mut p, &mut n),
456                    pair_name,
457                    dtype,
458                )?;
459                Ok(NvValue::UInt8Array(array_to_vec(p, n, pair_name, dtype)?))
460            }
461            data_type_t_DATA_TYPE_INT16_ARRAY => {
462                let mut p: *mut i16 = std::ptr::null_mut();
463                let mut n: uint_t = 0;
464                check_rc(
465                    nvpair_value_int16_array(nvp, &mut p, &mut n),
466                    pair_name,
467                    dtype,
468                )?;
469                Ok(NvValue::Int16Array(array_to_vec(p, n, pair_name, dtype)?))
470            }
471            data_type_t_DATA_TYPE_UINT16_ARRAY => {
472                let mut p: *mut u16 = std::ptr::null_mut();
473                let mut n: uint_t = 0;
474                check_rc(
475                    nvpair_value_uint16_array(nvp, &mut p, &mut n),
476                    pair_name,
477                    dtype,
478                )?;
479                Ok(NvValue::UInt16Array(array_to_vec(p, n, pair_name, dtype)?))
480            }
481            data_type_t_DATA_TYPE_INT32_ARRAY => {
482                let mut p: *mut i32 = std::ptr::null_mut();
483                let mut n: uint_t = 0;
484                check_rc(
485                    nvpair_value_int32_array(nvp, &mut p, &mut n),
486                    pair_name,
487                    dtype,
488                )?;
489                Ok(NvValue::Int32Array(array_to_vec(p, n, pair_name, dtype)?))
490            }
491            data_type_t_DATA_TYPE_UINT32_ARRAY => {
492                let mut p: *mut u32 = std::ptr::null_mut();
493                let mut n: uint_t = 0;
494                check_rc(
495                    nvpair_value_uint32_array(nvp, &mut p, &mut n),
496                    pair_name,
497                    dtype,
498                )?;
499                Ok(NvValue::UInt32Array(array_to_vec(p, n, pair_name, dtype)?))
500            }
501            data_type_t_DATA_TYPE_INT64_ARRAY => {
502                let mut p: *mut i64 = std::ptr::null_mut();
503                let mut n: uint_t = 0;
504                check_rc(
505                    nvpair_value_int64_array(nvp, &mut p, &mut n),
506                    pair_name,
507                    dtype,
508                )?;
509                Ok(NvValue::Int64Array(array_to_vec(p, n, pair_name, dtype)?))
510            }
511            data_type_t_DATA_TYPE_UINT64_ARRAY => {
512                let mut p: *mut u64 = std::ptr::null_mut();
513                let mut n: uint_t = 0;
514                check_rc(
515                    nvpair_value_uint64_array(nvp, &mut p, &mut n),
516                    pair_name,
517                    dtype,
518                )?;
519                Ok(NvValue::UInt64Array(array_to_vec(p, n, pair_name, dtype)?))
520            }
521            data_type_t_DATA_TYPE_STRING_ARRAY => {
522                let mut p: *mut *mut c_char = std::ptr::null_mut();
523                let mut n: uint_t = 0;
524                check_rc(
525                    nvpair_value_string_array(nvp, &mut p, &mut n),
526                    pair_name,
527                    dtype,
528                )?;
529                let ptrs = array_to_vec(p as *const *mut c_char, n, pair_name, dtype)?;
530                let mut strings = Vec::with_capacity(ptrs.len());
531                for &s in &ptrs {
532                    if s.is_null() {
533                        return Err(NvError::NullPointer {
534                            pair_name: pair_name.to_owned(),
535                            type_code: dtype,
536                        });
537                    }
538                    strings.push(CStr::from_ptr(s).to_string_lossy().into_owned());
539                }
540                Ok(NvValue::StringArray(strings))
541            }
542            data_type_t_DATA_TYPE_NVLIST_ARRAY => {
543                let mut p: *mut *mut nvlist_t = std::ptr::null_mut();
544                let mut n: uint_t = 0;
545                check_rc(
546                    nvpair_value_nvlist_array(nvp, &mut p, &mut n),
547                    pair_name,
548                    dtype,
549                )?;
550                let ptrs = array_to_vec(p as *const *mut nvlist_t, n, pair_name, dtype)?;
551                let mut lists = Vec::with_capacity(ptrs.len());
552                for &nvl in &ptrs {
553                    if nvl.is_null() {
554                        return Err(NvError::NullPointer {
555                            pair_name: pair_name.to_owned(),
556                            type_code: dtype,
557                        });
558                    }
559                    lists.push(NvList::from_raw(nvl)?);
560                }
561                Ok(NvValue::NvListArray(lists))
562            }
563            other => Ok(NvValue::Unknown { type_code: other }),
564        }
565    }
566}
567
568#[cfg(test)]
569mod tests {
570    use super::*;
571    use illumos_nvpair_sys::{
572        NV_UNIQUE_NAME, boolean_t, nvlist_add_boolean, nvlist_add_boolean_array,
573        nvlist_add_boolean_value, nvlist_add_byte, nvlist_add_byte_array, nvlist_add_double,
574        nvlist_add_hrtime, nvlist_add_int8, nvlist_add_int8_array, nvlist_add_int16,
575        nvlist_add_int16_array, nvlist_add_int32, nvlist_add_int32_array, nvlist_add_int64,
576        nvlist_add_int64_array, nvlist_add_nvlist, nvlist_add_nvlist_array, nvlist_add_string,
577        nvlist_add_string_array, nvlist_add_uint8, nvlist_add_uint8_array, nvlist_add_uint16,
578        nvlist_add_uint16_array, nvlist_add_uint32, nvlist_add_uint32_array, nvlist_add_uint64,
579        nvlist_add_uint64_array, nvlist_alloc, nvlist_free,
580    };
581    use std::ffi::CString;
582
583    /// RAII wrapper around a C nvlist for test construction.
584    struct NvListBuilder {
585        ptr: *mut nvlist_t,
586    }
587
588    impl NvListBuilder {
589        fn new() -> Self {
590            let mut ptr: *mut nvlist_t = std::ptr::null_mut();
591            let rc = unsafe { nvlist_alloc(&mut ptr, NV_UNIQUE_NAME, 0) };
592            assert_eq!(rc, 0, "nvlist_alloc failed");
593            assert!(!ptr.is_null());
594            NvListBuilder { ptr }
595        }
596
597        fn to_rust(&self) -> NvList {
598            unsafe { NvList::from_raw(self.ptr) }.expect("nvlist_to_rust failed")
599        }
600
601        fn add_boolean(&self, name: &str) -> &Self {
602            let cname = CString::new(name).unwrap();
603            let rc = unsafe { nvlist_add_boolean(self.ptr, cname.as_ptr()) };
604            assert_eq!(rc, 0, "nvlist_add_boolean failed for {name}");
605            self
606        }
607
608        fn add_boolean_value(&self, name: &str, val: bool) -> &Self {
609            let cname = CString::new(name).unwrap();
610            let cval: boolean_t = if val { 1 } else { 0 };
611            let rc = unsafe { nvlist_add_boolean_value(self.ptr, cname.as_ptr(), cval) };
612            assert_eq!(rc, 0, "nvlist_add_boolean_value failed for {name}");
613            self
614        }
615
616        fn add_byte(&self, name: &str, val: u8) -> &Self {
617            let cname = CString::new(name).unwrap();
618            let rc = unsafe { nvlist_add_byte(self.ptr, cname.as_ptr(), val) };
619            assert_eq!(rc, 0, "nvlist_add_byte failed for {name}");
620            self
621        }
622
623        fn add_int8(&self, name: &str, val: i8) -> &Self {
624            let cname = CString::new(name).unwrap();
625            let rc = unsafe { nvlist_add_int8(self.ptr, cname.as_ptr(), val) };
626            assert_eq!(rc, 0, "nvlist_add_int8 failed for {name}");
627            self
628        }
629
630        fn add_uint8(&self, name: &str, val: u8) -> &Self {
631            let cname = CString::new(name).unwrap();
632            let rc = unsafe { nvlist_add_uint8(self.ptr, cname.as_ptr(), val) };
633            assert_eq!(rc, 0, "nvlist_add_uint8 failed for {name}");
634            self
635        }
636
637        fn add_int16(&self, name: &str, val: i16) -> &Self {
638            let cname = CString::new(name).unwrap();
639            let rc = unsafe { nvlist_add_int16(self.ptr, cname.as_ptr(), val) };
640            assert_eq!(rc, 0, "nvlist_add_int16 failed for {name}");
641            self
642        }
643
644        fn add_uint16(&self, name: &str, val: u16) -> &Self {
645            let cname = CString::new(name).unwrap();
646            let rc = unsafe { nvlist_add_uint16(self.ptr, cname.as_ptr(), val) };
647            assert_eq!(rc, 0, "nvlist_add_uint16 failed for {name}");
648            self
649        }
650
651        fn add_int32(&self, name: &str, val: i32) -> &Self {
652            let cname = CString::new(name).unwrap();
653            let rc = unsafe { nvlist_add_int32(self.ptr, cname.as_ptr(), val) };
654            assert_eq!(rc, 0, "nvlist_add_int32 failed for {name}");
655            self
656        }
657
658        fn add_uint32(&self, name: &str, val: u32) -> &Self {
659            let cname = CString::new(name).unwrap();
660            let rc = unsafe { nvlist_add_uint32(self.ptr, cname.as_ptr(), val) };
661            assert_eq!(rc, 0, "nvlist_add_uint32 failed for {name}");
662            self
663        }
664
665        fn add_int64(&self, name: &str, val: i64) -> &Self {
666            let cname = CString::new(name).unwrap();
667            let rc = unsafe { nvlist_add_int64(self.ptr, cname.as_ptr(), val) };
668            assert_eq!(rc, 0, "nvlist_add_int64 failed for {name}");
669            self
670        }
671
672        fn add_uint64(&self, name: &str, val: u64) -> &Self {
673            let cname = CString::new(name).unwrap();
674            let rc = unsafe { nvlist_add_uint64(self.ptr, cname.as_ptr(), val) };
675            assert_eq!(rc, 0, "nvlist_add_uint64 failed for {name}");
676            self
677        }
678
679        fn add_double(&self, name: &str, val: f64) -> &Self {
680            let cname = CString::new(name).unwrap();
681            let rc = unsafe { nvlist_add_double(self.ptr, cname.as_ptr(), val) };
682            assert_eq!(rc, 0, "nvlist_add_double failed for {name}");
683            self
684        }
685
686        fn add_string(&self, name: &str, val: &str) -> &Self {
687            let cname = CString::new(name).unwrap();
688            let cval = CString::new(val).unwrap();
689            let rc = unsafe { nvlist_add_string(self.ptr, cname.as_ptr(), cval.as_ptr()) };
690            assert_eq!(rc, 0, "nvlist_add_string failed for {name}");
691            self
692        }
693
694        fn add_hrtime(&self, name: &str, val: i64) -> &Self {
695            let cname = CString::new(name).unwrap();
696            let rc = unsafe { nvlist_add_hrtime(self.ptr, cname.as_ptr(), val) };
697            assert_eq!(rc, 0, "nvlist_add_hrtime failed for {name}");
698            self
699        }
700
701        fn add_nvlist(&self, name: &str, child: &NvListBuilder) -> &Self {
702            let cname = CString::new(name).unwrap();
703            let rc = unsafe { nvlist_add_nvlist(self.ptr, cname.as_ptr(), child.ptr) };
704            assert_eq!(rc, 0, "nvlist_add_nvlist failed for {name}");
705            self
706        }
707
708        fn add_boolean_array(&self, name: &str, vals: &[bool]) -> &Self {
709            let cname = CString::new(name).unwrap();
710            let cvals: Vec<boolean_t> = vals.iter().map(|&v| if v { 1 } else { 0 }).collect();
711            let rc = unsafe {
712                nvlist_add_boolean_array(
713                    self.ptr,
714                    cname.as_ptr(),
715                    cvals.as_ptr() as *mut boolean_t,
716                    cvals.len() as uint_t,
717                )
718            };
719            assert_eq!(rc, 0, "nvlist_add_boolean_array failed for {name}");
720            self
721        }
722
723        fn add_byte_array(&self, name: &str, vals: &[u8]) -> &Self {
724            let cname = CString::new(name).unwrap();
725            let rc = unsafe {
726                nvlist_add_byte_array(
727                    self.ptr,
728                    cname.as_ptr(),
729                    vals.as_ptr() as *mut u8,
730                    vals.len() as uint_t,
731                )
732            };
733            assert_eq!(rc, 0, "nvlist_add_byte_array failed for {name}");
734            self
735        }
736
737        fn add_int8_array(&self, name: &str, vals: &[i8]) -> &Self {
738            let cname = CString::new(name).unwrap();
739            let rc = unsafe {
740                nvlist_add_int8_array(
741                    self.ptr,
742                    cname.as_ptr(),
743                    vals.as_ptr() as *mut i8,
744                    vals.len() as uint_t,
745                )
746            };
747            assert_eq!(rc, 0, "nvlist_add_int8_array failed for {name}");
748            self
749        }
750
751        fn add_uint8_array(&self, name: &str, vals: &[u8]) -> &Self {
752            let cname = CString::new(name).unwrap();
753            let rc = unsafe {
754                nvlist_add_uint8_array(
755                    self.ptr,
756                    cname.as_ptr(),
757                    vals.as_ptr() as *mut u8,
758                    vals.len() as uint_t,
759                )
760            };
761            assert_eq!(rc, 0, "nvlist_add_uint8_array failed for {name}");
762            self
763        }
764
765        fn add_int16_array(&self, name: &str, vals: &[i16]) -> &Self {
766            let cname = CString::new(name).unwrap();
767            let rc = unsafe {
768                nvlist_add_int16_array(
769                    self.ptr,
770                    cname.as_ptr(),
771                    vals.as_ptr() as *mut i16,
772                    vals.len() as uint_t,
773                )
774            };
775            assert_eq!(rc, 0, "nvlist_add_int16_array failed for {name}");
776            self
777        }
778
779        fn add_uint16_array(&self, name: &str, vals: &[u16]) -> &Self {
780            let cname = CString::new(name).unwrap();
781            let rc = unsafe {
782                nvlist_add_uint16_array(
783                    self.ptr,
784                    cname.as_ptr(),
785                    vals.as_ptr() as *mut u16,
786                    vals.len() as uint_t,
787                )
788            };
789            assert_eq!(rc, 0, "nvlist_add_uint16_array failed for {name}");
790            self
791        }
792
793        fn add_int32_array(&self, name: &str, vals: &[i32]) -> &Self {
794            let cname = CString::new(name).unwrap();
795            let rc = unsafe {
796                nvlist_add_int32_array(
797                    self.ptr,
798                    cname.as_ptr(),
799                    vals.as_ptr() as *mut i32,
800                    vals.len() as uint_t,
801                )
802            };
803            assert_eq!(rc, 0, "nvlist_add_int32_array failed for {name}");
804            self
805        }
806
807        fn add_uint32_array(&self, name: &str, vals: &[u32]) -> &Self {
808            let cname = CString::new(name).unwrap();
809            let rc = unsafe {
810                nvlist_add_uint32_array(
811                    self.ptr,
812                    cname.as_ptr(),
813                    vals.as_ptr() as *mut u32,
814                    vals.len() as uint_t,
815                )
816            };
817            assert_eq!(rc, 0, "nvlist_add_uint32_array failed for {name}");
818            self
819        }
820
821        fn add_int64_array(&self, name: &str, vals: &[i64]) -> &Self {
822            let cname = CString::new(name).unwrap();
823            let rc = unsafe {
824                nvlist_add_int64_array(
825                    self.ptr,
826                    cname.as_ptr(),
827                    vals.as_ptr() as *mut i64,
828                    vals.len() as uint_t,
829                )
830            };
831            assert_eq!(rc, 0, "nvlist_add_int64_array failed for {name}");
832            self
833        }
834
835        fn add_uint64_array(&self, name: &str, vals: &[u64]) -> &Self {
836            let cname = CString::new(name).unwrap();
837            let rc = unsafe {
838                nvlist_add_uint64_array(
839                    self.ptr,
840                    cname.as_ptr(),
841                    vals.as_ptr() as *mut u64,
842                    vals.len() as uint_t,
843                )
844            };
845            assert_eq!(rc, 0, "nvlist_add_uint64_array failed for {name}");
846            self
847        }
848
849        fn add_string_array(&self, name: &str, vals: &[&str]) -> &Self {
850            let cname = CString::new(name).unwrap();
851            let cvals: Vec<CString> = vals.iter().map(|s| CString::new(*s).unwrap()).collect();
852            let ptrs: Vec<*mut c_char> = cvals.iter().map(|c| c.as_ptr() as *mut c_char).collect();
853            let rc = unsafe {
854                nvlist_add_string_array(
855                    self.ptr,
856                    cname.as_ptr(),
857                    ptrs.as_ptr(),
858                    ptrs.len() as uint_t,
859                )
860            };
861            assert_eq!(rc, 0, "nvlist_add_string_array failed for {name}");
862            self
863        }
864
865        fn add_nvlist_array(&self, name: &str, children: &[&NvListBuilder]) -> &Self {
866            let cname = CString::new(name).unwrap();
867            let mut ptrs: Vec<*mut nvlist_t> = children.iter().map(|c| c.ptr).collect();
868            let rc = unsafe {
869                nvlist_add_nvlist_array(
870                    self.ptr,
871                    cname.as_ptr(),
872                    ptrs.as_mut_ptr(),
873                    ptrs.len() as uint_t,
874                )
875            };
876            assert_eq!(rc, 0, "nvlist_add_nvlist_array failed for {name}");
877            self
878        }
879    }
880
881    impl NvListBuilder {
882        /// Relinquish ownership and return the raw nvlist pointer. The
883        /// caller is then responsible for freeing it (or transferring
884        /// ownership to something that will).
885        fn take_ptr(self) -> *mut nvlist_t {
886            let ptr = self.ptr;
887            std::mem::forget(self);
888            ptr
889        }
890    }
891
892    impl Drop for NvListBuilder {
893        fn drop(&mut self) {
894            unsafe { nvlist_free(self.ptr) }
895        }
896    }
897
898    // ---- Scalar type tests ----
899
900    #[test]
901    fn test_boolean() {
902        let nvl = NvListBuilder::new();
903        nvl.add_boolean("flag");
904        let result = nvl.to_rust();
905        assert_eq!(result.lookup("flag"), Some(&NvValue::Boolean));
906    }
907
908    #[test]
909    fn test_boolean_value_true() {
910        let nvl = NvListBuilder::new();
911        nvl.add_boolean_value("enabled", true);
912        let result = nvl.to_rust();
913        assert_eq!(result.lookup("enabled"), Some(&NvValue::BooleanValue(true)));
914    }
915
916    #[test]
917    fn test_boolean_value_false() {
918        let nvl = NvListBuilder::new();
919        nvl.add_boolean_value("enabled", false);
920        let result = nvl.to_rust();
921        assert_eq!(
922            result.lookup("enabled"),
923            Some(&NvValue::BooleanValue(false))
924        );
925    }
926
927    #[test]
928    fn test_byte() {
929        let nvl = NvListBuilder::new();
930        nvl.add_byte("b", 0xAB);
931        let result = nvl.to_rust();
932        assert_eq!(result.lookup("b"), Some(&NvValue::Byte(0xAB)));
933    }
934
935    #[test]
936    fn test_int8() {
937        let nvl = NvListBuilder::new();
938        nvl.add_int8("v", i8::MIN);
939        let result = nvl.to_rust();
940        assert_eq!(result.lookup("v"), Some(&NvValue::Int8(i8::MIN)));
941    }
942
943    #[test]
944    fn test_uint8() {
945        let nvl = NvListBuilder::new();
946        nvl.add_uint8("v", u8::MAX);
947        let result = nvl.to_rust();
948        assert_eq!(result.lookup("v"), Some(&NvValue::UInt8(u8::MAX)));
949    }
950
951    #[test]
952    fn test_int16() {
953        let nvl = NvListBuilder::new();
954        nvl.add_int16("v", i16::MIN);
955        let result = nvl.to_rust();
956        assert_eq!(result.lookup("v"), Some(&NvValue::Int16(i16::MIN)));
957    }
958
959    #[test]
960    fn test_uint16() {
961        let nvl = NvListBuilder::new();
962        nvl.add_uint16("v", u16::MAX);
963        let result = nvl.to_rust();
964        assert_eq!(result.lookup("v"), Some(&NvValue::UInt16(u16::MAX)));
965    }
966
967    #[test]
968    fn test_int32() {
969        let nvl = NvListBuilder::new();
970        nvl.add_int32("v", i32::MIN);
971        let result = nvl.to_rust();
972        assert_eq!(result.lookup("v"), Some(&NvValue::Int32(i32::MIN)));
973    }
974
975    #[test]
976    fn test_uint32() {
977        let nvl = NvListBuilder::new();
978        nvl.add_uint32("v", u32::MAX);
979        let result = nvl.to_rust();
980        assert_eq!(result.lookup("v"), Some(&NvValue::UInt32(u32::MAX)));
981    }
982
983    #[test]
984    fn test_int64() {
985        let nvl = NvListBuilder::new();
986        nvl.add_int64("v", i64::MIN);
987        let result = nvl.to_rust();
988        assert_eq!(result.lookup("v"), Some(&NvValue::Int64(i64::MIN)));
989    }
990
991    #[test]
992    fn test_uint64() {
993        let nvl = NvListBuilder::new();
994        nvl.add_uint64("v", u64::MAX);
995        let result = nvl.to_rust();
996        assert_eq!(result.lookup("v"), Some(&NvValue::UInt64(u64::MAX)));
997    }
998
999    #[test]
1000    fn test_double() {
1001        let nvl = NvListBuilder::new();
1002        nvl.add_double("pi", std::f64::consts::PI);
1003        let result = nvl.to_rust();
1004        assert_eq!(
1005            result.lookup("pi"),
1006            Some(&NvValue::Double(std::f64::consts::PI))
1007        );
1008    }
1009
1010    #[test]
1011    fn test_string() {
1012        let nvl = NvListBuilder::new();
1013        nvl.add_string("greeting", "hello world");
1014        let result = nvl.to_rust();
1015        assert_eq!(
1016            result.lookup("greeting"),
1017            Some(&NvValue::String("hello world".into()))
1018        );
1019    }
1020
1021    #[test]
1022    fn test_hrtime() {
1023        let nvl = NvListBuilder::new();
1024        nvl.add_hrtime("ts", 123_456_789);
1025        let result = nvl.to_rust();
1026        assert_eq!(result.lookup("ts"), Some(&NvValue::Hrtime(123_456_789)));
1027    }
1028
1029    // ---- Array type tests ----
1030
1031    #[test]
1032    fn test_boolean_array() {
1033        let nvl = NvListBuilder::new();
1034        nvl.add_boolean_array("flags", &[true, false, true]);
1035        let result = nvl.to_rust();
1036        assert_eq!(
1037            result.lookup("flags"),
1038            Some(&NvValue::BooleanArray(vec![true, false, true]))
1039        );
1040    }
1041
1042    #[test]
1043    fn test_byte_array() {
1044        let nvl = NvListBuilder::new();
1045        nvl.add_byte_array("data", &[1, 2, 3]);
1046        let result = nvl.to_rust();
1047        assert_eq!(
1048            result.lookup("data"),
1049            Some(&NvValue::ByteArray(vec![1, 2, 3]))
1050        );
1051    }
1052
1053    #[test]
1054    fn test_int8_array() {
1055        let nvl = NvListBuilder::new();
1056        nvl.add_int8_array("vals", &[i8::MIN, 0, i8::MAX]);
1057        let result = nvl.to_rust();
1058        assert_eq!(
1059            result.lookup("vals"),
1060            Some(&NvValue::Int8Array(vec![i8::MIN, 0, i8::MAX]))
1061        );
1062    }
1063
1064    #[test]
1065    fn test_uint8_array() {
1066        let nvl = NvListBuilder::new();
1067        nvl.add_uint8_array("vals", &[0, 128, u8::MAX]);
1068        let result = nvl.to_rust();
1069        assert_eq!(
1070            result.lookup("vals"),
1071            Some(&NvValue::UInt8Array(vec![0, 128, u8::MAX]))
1072        );
1073    }
1074
1075    #[test]
1076    fn test_int16_array() {
1077        let nvl = NvListBuilder::new();
1078        nvl.add_int16_array("vals", &[i16::MIN, 0, i16::MAX]);
1079        let result = nvl.to_rust();
1080        assert_eq!(
1081            result.lookup("vals"),
1082            Some(&NvValue::Int16Array(vec![i16::MIN, 0, i16::MAX]))
1083        );
1084    }
1085
1086    #[test]
1087    fn test_uint16_array() {
1088        let nvl = NvListBuilder::new();
1089        nvl.add_uint16_array("vals", &[0, u16::MAX]);
1090        let result = nvl.to_rust();
1091        assert_eq!(
1092            result.lookup("vals"),
1093            Some(&NvValue::UInt16Array(vec![0, u16::MAX]))
1094        );
1095    }
1096
1097    #[test]
1098    fn test_int32_array() {
1099        let nvl = NvListBuilder::new();
1100        nvl.add_int32_array("vals", &[i32::MIN, 0, i32::MAX]);
1101        let result = nvl.to_rust();
1102        assert_eq!(
1103            result.lookup("vals"),
1104            Some(&NvValue::Int32Array(vec![i32::MIN, 0, i32::MAX]))
1105        );
1106    }
1107
1108    #[test]
1109    fn test_uint32_array() {
1110        let nvl = NvListBuilder::new();
1111        nvl.add_uint32_array("vals", &[0, u32::MAX]);
1112        let result = nvl.to_rust();
1113        assert_eq!(
1114            result.lookup("vals"),
1115            Some(&NvValue::UInt32Array(vec![0, u32::MAX]))
1116        );
1117    }
1118
1119    #[test]
1120    fn test_int64_array() {
1121        let nvl = NvListBuilder::new();
1122        nvl.add_int64_array("vals", &[i64::MIN, 0, i64::MAX]);
1123        let result = nvl.to_rust();
1124        assert_eq!(
1125            result.lookup("vals"),
1126            Some(&NvValue::Int64Array(vec![i64::MIN, 0, i64::MAX]))
1127        );
1128    }
1129
1130    #[test]
1131    fn test_uint64_array() {
1132        let nvl = NvListBuilder::new();
1133        nvl.add_uint64_array("vals", &[0, u64::MAX]);
1134        let result = nvl.to_rust();
1135        assert_eq!(
1136            result.lookup("vals"),
1137            Some(&NvValue::UInt64Array(vec![0, u64::MAX]))
1138        );
1139    }
1140
1141    #[test]
1142    fn test_string_array() {
1143        let nvl = NvListBuilder::new();
1144        nvl.add_string_array("names", &["alpha", "beta", "gamma"]);
1145        let result = nvl.to_rust();
1146        assert_eq!(
1147            result.lookup("names"),
1148            Some(&NvValue::StringArray(vec![
1149                "alpha".into(),
1150                "beta".into(),
1151                "gamma".into(),
1152            ]))
1153        );
1154    }
1155
1156    #[test]
1157    fn test_nvlist_array() {
1158        let child1 = NvListBuilder::new();
1159        child1.add_string("name", "first");
1160        let child2 = NvListBuilder::new();
1161        child2.add_string("name", "second");
1162
1163        let nvl = NvListBuilder::new();
1164        nvl.add_nvlist_array("items", &[&child1, &child2]);
1165
1166        let result = nvl.to_rust();
1167        let items = result.lookup("items").unwrap();
1168        if let NvValue::NvListArray(arr) = items {
1169            assert_eq!(arr.len(), 2);
1170            assert_eq!(
1171                arr[0].lookup("name"),
1172                Some(&NvValue::String("first".into()))
1173            );
1174            assert_eq!(
1175                arr[1].lookup("name"),
1176                Some(&NvValue::String("second".into()))
1177            );
1178        } else {
1179            panic!("expected NvListArray, got {items:?}");
1180        }
1181    }
1182
1183    // ---- Structural / edge case tests ----
1184
1185    #[test]
1186    fn test_empty_nvlist() {
1187        let nvl = NvListBuilder::new();
1188        let result = nvl.to_rust();
1189        assert!(result.is_empty());
1190        assert_eq!(result.len(), 0);
1191    }
1192
1193    #[test]
1194    fn test_multiple_pairs_in_order() {
1195        let nvl = NvListBuilder::new();
1196        nvl.add_string("first", "a");
1197        nvl.add_int32("second", 42);
1198        nvl.add_boolean("third");
1199        nvl.add_uint64("fourth", 999);
1200        nvl.add_double("fifth", 2.5);
1201
1202        let result = nvl.to_rust();
1203        assert_eq!(result.len(), 5);
1204
1205        let pairs: Vec<_> = result.iter().collect();
1206        assert_eq!(pairs[0].0, "first");
1207        assert_eq!(pairs[1].0, "second");
1208        assert_eq!(pairs[2].0, "third");
1209        assert_eq!(pairs[3].0, "fourth");
1210        assert_eq!(pairs[4].0, "fifth");
1211
1212        assert_eq!(*pairs[0].1, NvValue::String("a".into()));
1213        assert_eq!(*pairs[1].1, NvValue::Int32(42));
1214        assert_eq!(*pairs[2].1, NvValue::Boolean);
1215        assert_eq!(*pairs[3].1, NvValue::UInt64(999));
1216        assert_eq!(*pairs[4].1, NvValue::Double(2.5));
1217    }
1218
1219    #[test]
1220    fn test_nested_nvlist() {
1221        let inner = NvListBuilder::new();
1222        inner.add_string("key", "value");
1223
1224        let outer = NvListBuilder::new();
1225        outer.add_nvlist("child", &inner);
1226
1227        let result = outer.to_rust();
1228        if let Some(NvValue::NvList(child)) = result.lookup("child") {
1229            assert_eq!(child.lookup("key"), Some(&NvValue::String("value".into())));
1230        } else {
1231            panic!("expected nested NvList");
1232        }
1233    }
1234
1235    #[test]
1236    fn test_deeply_nested_nvlist() {
1237        let innermost = NvListBuilder::new();
1238        innermost.add_string("depth", "three");
1239
1240        let middle = NvListBuilder::new();
1241        middle.add_nvlist("inner", &innermost);
1242
1243        let outer = NvListBuilder::new();
1244        outer.add_nvlist("middle", &middle);
1245
1246        let result = outer.to_rust();
1247        if let Some(NvValue::NvList(mid)) = result.lookup("middle") {
1248            if let Some(NvValue::NvList(inn)) = mid.lookup("inner") {
1249                assert_eq!(inn.lookup("depth"), Some(&NvValue::String("three".into())));
1250            } else {
1251                panic!("expected inner NvList");
1252            }
1253        } else {
1254            panic!("expected middle NvList");
1255        }
1256    }
1257
1258    #[test]
1259    fn test_empty_string() {
1260        let nvl = NvListBuilder::new();
1261        nvl.add_string("empty", "");
1262        let result = nvl.to_rust();
1263        assert_eq!(
1264            result.lookup("empty"),
1265            Some(&NvValue::String(String::new()))
1266        );
1267    }
1268
1269    #[test]
1270    fn test_lookup_missing_key() {
1271        let nvl = NvListBuilder::new();
1272        nvl.add_string("exists", "yes");
1273        let result = nvl.to_rust();
1274        assert_eq!(
1275            result.lookup("exists"),
1276            Some(&NvValue::String("yes".into()))
1277        );
1278        assert_eq!(result.lookup("missing"), None);
1279    }
1280
1281    // ---- OwnedNvList tests ----
1282
1283    #[test]
1284    fn test_owned_nvlist_drop_frees() {
1285        let b = NvListBuilder::new();
1286        b.add_string("name", "value");
1287        let raw = b.take_ptr();
1288        let owned = unsafe { OwnedNvList::from_raw(raw) };
1289        drop(owned);
1290    }
1291
1292    #[test]
1293    fn test_owned_nvlist_as_raw() {
1294        let b = NvListBuilder::new();
1295        let raw = b.take_ptr();
1296        let owned = unsafe { OwnedNvList::from_raw(raw) };
1297        assert_eq!(owned.as_raw(), raw);
1298    }
1299
1300    #[test]
1301    fn test_owned_nvlist_into_raw_does_not_free() {
1302        let b = NvListBuilder::new();
1303        b.add_string("name", "value");
1304        let raw = b.take_ptr();
1305        let owned = unsafe { OwnedNvList::from_raw(raw) };
1306        let recovered = owned.into_raw();
1307        assert_eq!(recovered, raw);
1308        // The nvlist is still live; free it manually.
1309        unsafe { nvlist_free(recovered) };
1310    }
1311
1312    #[test]
1313    fn test_owned_nvlist_inspect() {
1314        let b = NvListBuilder::new();
1315        b.add_string("greeting", "hello");
1316        b.add_int32("count", 42);
1317        let raw = b.take_ptr();
1318        let owned = unsafe { OwnedNvList::from_raw(raw) };
1319        let inspected = owned.inspect().expect("inspect failed");
1320        assert_eq!(
1321            inspected.lookup("greeting"),
1322            Some(&NvValue::String("hello".into()))
1323        );
1324        assert_eq!(inspected.lookup("count"), Some(&NvValue::Int32(42)));
1325    }
1326
1327    #[test]
1328    fn test_owned_nvlist_inspect_repeated() {
1329        let b = NvListBuilder::new();
1330        b.add_string("k", "v");
1331        let raw = b.take_ptr();
1332        let owned = unsafe { OwnedNvList::from_raw(raw) };
1333        let first = owned.inspect().unwrap();
1334        let second = owned.inspect().unwrap();
1335        assert_eq!(first, second);
1336    }
1337}