Skip to main content

nix_bindings/
error.rs

1//! Error types and FFI error-handling helpers.
2//!
3//! Re-exported at the crate root as [`crate::Error`] and [`crate::Result`].
4
5use std::{ffi::CStr, fmt};
6
7use crate::sys;
8
9/// Result type for Nix operations.
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Error types for Nix operations.
13#[derive(Debug)]
14pub enum Error {
15  /// Unknown error from Nix C API.
16  Unknown(String),
17
18  /// Overflow error.
19  Overflow,
20
21  /// Key not found error.
22  KeyNotFound(String),
23
24  /// List index out of bounds.
25  IndexOutOfBounds {
26    /// The index that was requested.
27    index:  usize,
28    /// The actual length of the list.
29    length: usize,
30  },
31
32  /// Nix evaluation error.
33  EvalError(String),
34
35  /// Invalid value type conversion.
36  InvalidType {
37    /// Expected type.
38    expected: &'static str,
39    /// Actual type.
40    actual:   String,
41  },
42  /// Null pointer error.
43  NullPointer,
44
45  /// String conversion error.
46  StringConversion(std::ffi::NulError),
47}
48
49impl fmt::Display for Error {
50  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51    match self {
52      Error::Unknown(msg) => write!(f, "Unknown error: {msg}"),
53      Error::Overflow => write!(f, "Overflow error"),
54      Error::KeyNotFound(key) => write!(f, "Key not found: {key}"),
55      Error::IndexOutOfBounds { index, length } => {
56        write!(f, "Index out of bounds: index {index}, length {length}")
57      },
58      Error::EvalError(msg) => write!(f, "Evaluation error: {msg}"),
59      Error::InvalidType { expected, actual } => {
60        write!(f, "Invalid type: expected {expected}, got {actual}")
61      },
62      Error::NullPointer => write!(f, "Null pointer error"),
63      Error::StringConversion(e) => write!(f, "String conversion error: {e}"),
64    }
65  }
66}
67
68impl std::error::Error for Error {}
69
70impl From<std::ffi::NulError> for Error {
71  fn from(e: std::ffi::NulError) -> Self {
72    Error::StringConversion(e)
73  }
74}
75
76/// Extract a string from a Nix context using a callback-based API.
77///
78/// Many Nix C API functions return strings via callbacks. This helper
79/// makes that pattern ergonomic.
80///
81/// # Safety
82///
83/// `call` must invoke `callback` with a valid string pointer and length.
84#[cfg(feature = "store")]
85pub(crate) unsafe fn string_from_callback<F>(call: F) -> Option<String>
86where
87  F: FnOnce(sys::nix_get_string_callback, *mut std::os::raw::c_void),
88{
89  unsafe extern "C" fn collect(
90    start: *const std::os::raw::c_char,
91    n: std::os::raw::c_uint,
92    user_data: *mut std::os::raw::c_void,
93  ) {
94    let result = unsafe { &mut *(user_data as *mut Option<String>) };
95    if !start.is_null() {
96      let bytes =
97        unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
98      *result = std::str::from_utf8(bytes).ok().map(|s| s.to_owned());
99    }
100  }
101
102  let mut result: Option<String> = None;
103  let user_data = &mut result as *mut _ as *mut std::os::raw::c_void;
104  call(Some(collect), user_data);
105  result
106}
107
108/// Check a Nix error code and convert to `Result`, extracting the real
109/// error message from the context.
110#[cfg(feature = "store")]
111pub(crate) fn check_err(
112  ctx: *mut sys::nix_c_context,
113  err: sys::nix_err,
114) -> Result<()> {
115  if err == sys::nix_err_NIX_OK {
116    return Ok(());
117  }
118
119  // Extract the real error message from the context.
120  // nix_err_msg returns a borrowed pointer valid until the next Nix call.
121  // We must copy it to a String immediately.
122  let msg = unsafe {
123    let ptr = sys::nix_err_msg(std::ptr::null_mut(), ctx, std::ptr::null_mut());
124    if ptr.is_null() {
125      None
126    } else {
127      Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
128    }
129  };
130
131  // For NIX_ERR_NIX_ERROR, also try to get the richer info message.
132  let detail = if err == sys::nix_err_NIX_ERR_NIX_ERROR {
133    unsafe {
134      string_from_callback(|cb, ud| {
135        sys::nix_err_info_msg(std::ptr::null_mut(), ctx, cb, ud);
136      })
137    }
138  } else {
139    None
140  };
141
142  // Decorate with the symbolic error name (e.g. "NixError", "Key",
143  // "Overflow") when the API exposes one for this code. Improves
144  // diagnostics when the message itself is empty or generic.
145  let name = unsafe {
146    string_from_callback(|cb, ud| {
147      sys::nix_err_name(std::ptr::null_mut(), ctx, cb, ud);
148    })
149  };
150
151  let base_message = detail
152    .or(msg)
153    .unwrap_or_else(|| format!("Nix error code: {err}"));
154  let message = match name {
155    Some(n) if !n.is_empty() => format!("[{n}] {base_message}"),
156    _ => base_message,
157  };
158
159  match err {
160    sys::nix_err_NIX_ERR_UNKNOWN => Err(Error::Unknown(message)),
161    sys::nix_err_NIX_ERR_OVERFLOW => Err(Error::Overflow),
162    sys::nix_err_NIX_ERR_KEY => Err(Error::KeyNotFound(message)),
163    sys::nix_err_NIX_ERR_NIX_ERROR => Err(Error::EvalError(message)),
164    _ => Err(Error::Unknown(message)),
165  }
166}