this is how i stash right? /s

This commit is contained in:
do butterflies cry? 2026-03-21 10:26:27 +10:00
parent e437571af1
commit fcc8fede11
Signed by: cry
GPG key ID: F68745A836CA0412
31 changed files with 1217 additions and 858 deletions

7
Cargo.lock generated
View file

@ -192,6 +192,7 @@ version = "0.1.0"
dependencies = [
"nixide-sys",
"serial_test",
"stdext",
]
[[package]]
@ -383,6 +384,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stdext"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1"
[[package]]
name = "syn"
version = "2.0.117"

18
TODO.md
View file

@ -2,3 +2,21 @@
- [ ] replace all `use nixide_sys as sys;` -> `use crate::sys;`
- [ ] store NonNull pointers in structs!
- [ ] improve documentation situation on context.rs
- [ ] rename `as_ptr()` to `as_inner_ptr()` or `inner_ptr()`?
- [ ] ^^^ this fn should be added to a trait (maybe just `trait NixStructWrapper : AsPtr { ... }`)
- [ ] ^^^ also make `as_ptr()` public
- [ ] add mutexs and make the library thread safe!!
- [ ] grep all `self.inner.as_ptr()` calls and replace them with `self.as_ptr()`
- [ ] `ErrorContext::peak` should return `Result<(), NixideError>` **not** `Option<NixideError>`
- [ ] `self.expect_type` should instead be a macro to preserve the trace macro location
- [ ] make `Value` an enum instead because like duhh
- [ ] ensure we're always calling `ctx.peak()` unless it's ACTUALLY not necessary
- [ ] replace *most* calls to `ErrorContext::peak()` with `ErrorContext::pop()`

View file

@ -25,6 +25,7 @@ gc = []
[dependencies]
nixide-sys = { path = "../nixide-sys", version = "0.1.0" }
stdext = "0.3.3"
[dev-dependencies]
serial_test = "3.4.0"

View file

@ -1,233 +0,0 @@
// XXX: TODO: create wrappers methods to access more than just `info->msg()`
// struct ErrorInfo
// {
// Verbosity level;
// HintFmt msg;
// std::shared_ptr<const Pos> pos;
// std::list<Trace> traces;
// /**
// * Some messages are generated directly by expressions; notably `builtins.warn`, `abort`, `throw`.
// * These may be rendered differently, so that users can distinguish them.
// */
// bool isFromExpr = false;
// /**
// * Exit status.
// */
// unsigned int status = 1;
// Suggestions suggestions;
// static std::optional<std::string> programName;
// };
use std::ffi::{c_char, CStr};
use std::ptr::{null_mut, NonNull};
use crate::error::NixErrorCode;
use crate::sys;
use crate::util::bindings::wrap_libnix_string_callback;
// XXX: TODO: change this to a `Result<T, NixError>`
type NixResult<T> = Result<T, NixErrorCode>;
#[derive(Debug, Clone)]
pub struct NixError {
pub err: NixErrorCode,
pub name: String,
pub msg: String,
pub info_msg: String,
}
/// This object stores error state.
///
/// Passed as a first parameter to functions that can fail, to store error
/// information.
///
/// # Warning
///
/// These can be reused between different function calls,
/// but make sure not to use them for multiple calls simultaneously
/// (which can happen in callbacks).
///
/// # `libnix` API Internals
///
/// ```cpp
/// struct nix_c_context
/// {
/// nix_err last_err_code = NIX_OK;
/// /* WARNING: The last error message. Always check last_err_code.
/// WARNING: This may not have been cleared, so that clearing is fast. */
/// std::optional<std::string> last_err = {};
/// std::optional<nix::ErrorInfo> info = {};
/// std::string name = "";
/// };
/// ```
///
/// The [sys::nix_c_context] struct is laid out so that it can also be
/// cast to a [sys::nix_err] to inspect directly:
/// ```c
/// assert(*(nix_err*)ctx == NIX_OK);
/// ```
///
pub struct ErrorContext {
inner: NonNull<sys::nix_c_context>,
}
impl ErrorContext {
/// Create a new Nix context.
///
/// This initializes the Nix C API context and the required libraries.
///
/// # Errors
///
/// Returns an error if context creation or library initialization fails.
pub fn new() -> Result<Self, NixErrorCode> {
// SAFETY: nix_c_context_create is safe to call
let ctx_ptr = unsafe { sys::nix_c_context_create() };
let ctx = ErrorContext {
inner: NonNull::new(ctx_ptr).ok_or(NixErrorCode::NullPtr {
location: "nix_c_context_create",
})?,
};
// Initialize required libraries
// XXX: TODO: move this to a separate init function (maybe a Nix::init() function)
// unsafe {
// NixErrorCode::from(
// sys::nix_libutil_init(ctx.inner.as_ptr()),
// "nix_libutil_init",
// )?;
// NixErrorCode::from(
// sys::nix_libstore_init(ctx.inner.as_ptr()),
// "nix_libstore_init",
// )?;
// NixErrorCode::from(
// sys::nix_libexpr_init(ctx.inner.as_ptr()),
// "nix_libexpr_init",
// )?;
// };
Ok(ctx)
}
/// Get the raw context pointer.
///
/// # Safety
///
/// Although this function isn't inherently `unsafe`, it is
/// marked as such intentionally to force calls to be wrapped
/// in `unsafe` blocks for clarity.
pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_c_context {
self.inner.as_ptr()
}
/// Check the error code and return an error if it's not `NIX_OK`.
///
/// We recommend to use `check_call!` if possible.
pub fn peak(&self) -> Option<NixError> {
// NixError::from( unsafe { sys::nix_err_code(self.as_ptr())}, "");
// let err = unsafe { sys::nix_err_code(self.inner.as_ptr()) };
// if err != sys::nix_err_NIX_OK {
// // msgp is a borrowed pointer (pointing into the context), so we don't need to free it
// let msgp = unsafe { sys::nix_err_msg(null_mut(), self.inner.as_ptr(), null_mut()) };
// // Turn the i8 pointer into a Rust string by copying
// let msg: &str = unsafe { core::ffi::CStr::from_ptr(msgp).to_str()? };
// bail!("{}", msg);
// }
// Ok(())
let result = self.get_code();
match result {
Ok(()) => None,
Err(err) => Some(NixError {
err,
name: self.get_name()?,
msg: self.get_msg()?,
info_msg: self.get_info_msg()?,
}),
}
}
pub fn pop(&mut self) -> Option<NixError> {
let error = self.peak();
if error.is_some() {
self.clear();
}
error
}
pub fn clear(&mut self) {
unsafe {
// NOTE: previous nixops4 used the line: (maybe for compatability with old versions?)
// sys::nix_set_err_msg(self.inner.as_ptr(), sys::nix_err_NIX_OK, c"".as_ptr());
sys::nix_clear_err(self.as_ptr());
}
}
///
/// Never fails
pub(crate) fn get_code(&self) -> Result<(), NixErrorCode> {
NixErrorCode::from(unsafe { sys::nix_err_code(self.as_ptr()) }, "nix_err_code")
}
/// Returns None if no [self.code] is [sys::nix_err_NIX_OK].
pub(crate) fn get_name(&self) -> Option<String> {
unsafe {
let ctx = null_mut();
// NOTE: an Err here only occurs when "Last error was not a nix error"
wrap_libnix_string_callback("nix_err_name", |callback, user_data| {
sys::nix_err_name(ctx, self.as_ptr(), Some(callback), user_data)
})
.ok()
}
}
/// Returns None if no [self.code] is [sys::nix_err_NIX_OK].
/// # Note
/// On failure [sys::nix_err_name] does the following if the error
/// has the error code [sys::nix_err_NIX_OK]:
/// ```c
/// nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message");
/// return nullptr;
/// ```
/// Hence we can just test whether the returned pointer is a `NULL` pointer,
/// and avoid passing in a [sys::nix_c_context] struct.
pub(crate) fn get_msg(&self) -> Option<String> {
unsafe {
let ctx = null_mut();
let msg_ptr: *const c_char = sys::nix_err_msg(ctx, self.as_ptr(), null_mut());
if msg_ptr.is_null() {
return None;
}
match CStr::from_ptr(msg_ptr).to_str() {
Ok(msg_str) => Some(msg_str.to_string()),
Err(_) => None,
}
}
}
/// Returns None if no [self.code] is [sys::nix_err_NIX_OK].
pub(crate) fn get_info_msg(&self) -> Option<String> {
unsafe {
let ctx = null_mut();
// NOTE: an Err here only occurs when "Last error was not a nix error"
wrap_libnix_string_callback("nix_err_name", |callback, user_data| {
sys::nix_err_info_msg(ctx, self.as_ptr(), Some(callback), user_data)
})
.ok()
}
}
}
impl Drop for ErrorContext {
fn drop(&mut self) {
// SAFETY: We own the context and it's valid until drop
unsafe {
sys::nix_c_context_free(self.inner.as_ptr());
}
}
}

View file

@ -1,183 +0,0 @@
use std::ffi::NulError;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::ptr::NonNull;
use crate::sys;
/// Standard (nix_err) and some additional error codes
/// produced by the libnix C API.
#[derive(Debug, Clone)]
pub enum NixErrorCode {
/// A generic Nix error occurred.
///
/// # Reason
///
/// This error code is returned when a generic Nix error occurred during the
/// function execution.
NixError { location: &'static str },
/// An overflow error occurred.
///
/// # Reason
///
/// This error code is returned when an overflow error occurred during the
/// function execution.
Overflow { location: &'static str },
/// A key/index access error occurred in C API functions.
///
/// # Reason
///
/// This error code is returned when accessing a key, index, or identifier that
/// does not exist in C API functions. Common scenarios include:
/// - Setting keys that don't exist (nix_setting_get, nix_setting_set)
/// - List indices that are out of bounds (nix_get_list_byidx*)
/// - Attribute names that don't exist (nix_get_attr_byname*)
/// - Attribute indices that are out of bounds (nix_get_attr_byidx*, nix_get_attr_name_byidx)
///
/// This error typically indicates incorrect usage or assumptions about data structure
/// contents, rather than internal Nix evaluation errors.
///
/// # Note
///
/// This error code should ONLY be returned by C API functions themselves,
/// not by underlying Nix evaluation. For example, evaluating `{}.foo` in Nix
/// will throw a normal error (NIX_ERR_NIX_ERROR), not NIX_ERR_KEY.
KeyNotFound {
location: &'static str,
key: Option<String>,
},
/// An unknown error occurred.
///
/// # Reason
///
/// This error code is returned when an unknown error occurred during the
/// function execution.
Unknown {
location: &'static str,
reason: String,
},
/// An undocumented error occurred.
///
/// # Reason
///
/// The libnix C API defines `enum nix_err` as a signed integer value.
/// In the (unexpected) event libnix returns an error code with an
/// invalid enum value, or one I new addition I didn't know existed,
/// then an [NixError::Undocumented] is considered to have occurred.
Undocumented {
location: &'static str,
err_code: sys::nix_err,
},
//////////////////////
// NON-STANDARD ERRORS
//////////////////////
/// NulError
NulError { location: &'static str },
/// Non-standard
NullPtr { location: &'static str },
/// Invalid Argument
InvalidArg {
location: &'static str,
reason: &'static str, // XXX: TODO: make this a String
},
/// Invalid Type
InvalidType {
location: &'static str,
expected: &'static str,
got: String,
},
}
impl NixErrorCode {
pub fn from(err_code: sys::nix_err, location: &'static str) -> Result<(), NixErrorCode> {
#[allow(nonstandard_style)]
match err_code {
sys::nix_err_NIX_OK => Ok(()),
sys::nix_err_NIX_ERR_OVERFLOW => Err(NixErrorCode::Overflow { location }),
sys::nix_err_NIX_ERR_KEY => Err(NixErrorCode::KeyNotFound {
location,
key: None,
}),
sys::nix_err_NIX_ERR_NIX_ERROR => Err(NixErrorCode::NixError { location }),
sys::nix_err_NIX_ERR_UNKNOWN => Err(NixErrorCode::Unknown {
location,
reason: "Unknown error occurred".to_string(),
}),
_ => Err(NixErrorCode::Undocumented { location, err_code }),
}
}
pub fn from_nulerror<T>(
result: Result<T, NulError>,
location: &'static str,
) -> Result<T, Self> {
result.or(Err(NixErrorCode::NulError { location }))
}
pub fn new_nonnull<T>(ptr: *mut T, location: &'static str) -> Result<NonNull<T>, Self>
where
T: Sized,
{
NonNull::new(ptr).ok_or(NixErrorCode::NullPtr { location })
}
}
impl std::error::Error for NixErrorCode {}
impl Display for NixErrorCode {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let msg = match self {
NixErrorCode::NixError { location } => {
format!("[libnix] Generic error (at location `{location}`)")
}
NixErrorCode::Overflow { location } => {
format!("[libnix] Overflow error (at location `{location}`)")
}
NixErrorCode::KeyNotFound { location, key } => format!(
"[libnix] Key not found {} (at location `{location}`)",
match key {
Some(key) => format!("`{key}`"),
None => "".to_owned(),
}
),
NixErrorCode::Unknown { location, reason } => {
format!("Unknown error \"{reason}\" (at location `{location}`)")
}
NixErrorCode::Undocumented { location, err_code } => {
format!(
"[libnix] An undocumented nix_err was returned with {err_code} (at location `{location}`)"
)
}
NixErrorCode::NulError { location } => {
format!("Nul error (at location `{location}`)")
}
NixErrorCode::NullPtr { location } => {
format!("[libnix] Null pointer (at location `{location}`)")
}
NixErrorCode::InvalidArg { location, reason } => {
format!("Invalid argument \"{reason}\" (at location `{location}`)")
}
NixErrorCode::InvalidType {
location,
expected,
got,
} => {
format!("Invalid type, expected \"{expected}\" ${got} (at location `{location}`)")
}
};
write!(f, "{msg}")
}
}

View file

@ -0,0 +1,300 @@
// XXX: TODO: create wrappers methods to access more than just `info->msg()`
// struct ErrorInfo
// {
// Verbosity level;
// HintFmt msg;
// std::shared_ptr<const Pos> pos;
// std::list<Trace> traces;
// /**
// * Some messages are generated directly by expressions; notably `builtins.warn`, `abort`, `throw`.
// * These may be rendered differently, so that users can distinguish them.
// */
// bool isFromExpr = false;
// /**
// * Exit status.
// */
// unsigned int status = 1;
// Suggestions suggestions;
// static std::optional<std::string> programName;
// };
use std::ffi::c_uint;
use std::ptr::NonNull;
use super::NixideError;
use crate::sys;
use crate::util::bindings::wrap_libnix_string_callback;
use crate::util::wrappers::AsInnerPtr;
use crate::util::CCharPtrNixExt;
/// This object stores error state.
///
/// Passed as a first parameter to functions that can fail, to store error
/// information.
///
/// # Warning
///
/// These can be reused between different function calls,
/// but make sure not to use them for multiple calls simultaneously
/// (which can happen in callbacks).
///
/// # Nix C++ API Internals
///
/// ```cpp
/// struct nix_c_context
/// {
/// nix_err last_err_code = NIX_OK;
/// /* WARNING: The last error message. Always check last_err_code.
/// WARNING: This may not have been cleared, so that clearing is fast. */
/// std::optional<std::string> last_err = {};
/// std::optional<nix::ErrorInfo> info = {};
/// std::string name = "";
/// };
/// ```
///
/// The [sys::nix_c_context] struct is laid out so that it can also be
/// cast to a [sys::nix_err] to inspect directly:
/// ```c
/// assert(*(nix_err*)ctx == NIX_OK);
/// ```
pub(crate) struct ErrorContext {
// XXX: TODO: add a RwLock to this (maybe Arc<RwLock>? or is that excessive?)
inner: NonNull<sys::nix_c_context>,
}
impl AsInnerPtr<sys::nix_c_context> for ErrorContext {
unsafe fn as_ptr(&self) -> *mut sys::nix_c_context {
self.inner.as_ptr()
}
}
impl ErrorContext {
/// Create a new error context.
///
/// # Errors
///
/// Returns an error if no memory can be allocated for
/// the underlying [sys::nix_c_context] struct.
pub fn new() -> Self {
match NonNull::new(unsafe { sys::nix_c_context_create() }) {
Some(inner) => ErrorContext { inner },
None => panic!("[nixide] CRITICAL FAILURE: Out-Of-Memory condition reached - `sys::nix_c_context_create` allocation failed!"),
}
// Initialize required libraries
// XXX: TODO: move this to a separate init function (maybe a Nix::init() function)
// unsafe {
// NixErrorCode::from(
// sys::nix_libutil_init(ctx.inner.as_ptr()),
// "nix_libutil_init",
// )?;
// NixErrorCode::from(
// sys::nix_libstore_init(ctx.inner.as_ptr()),
// "nix_libstore_init",
// )?;
// NixErrorCode::from(
// sys::nix_libexpr_init(ctx.inner.as_ptr()),
// "nix_libexpr_init",
// )?;
// };
}
/// Check the error code and return an error if it's not `NIX_OK`.
pub fn peak(&self) -> Option<NixideError> {
NixideError::from_error_context(self)
}
///
/// Equivalent to running `self.peak()` then `self.clear()`
pub fn pop(&mut self) -> Option<NixideError> {
self.get_err().and_then(|_| {
let error = self.peak();
self.clear();
error
})
}
/// # Nix C++ API Internals
///
/// ```cpp
/// void nix_clear_err(nix_c_context * context)
/// {
/// if (context)
/// context->last_err_code = NIX_OK;
/// }
/// ```
///
/// `nix_clear_err` only modifies the `last_err_code`, it does not
/// clear all attributes of a `nix_c_context` struct. Hence all uses
/// of `nix_c_context` must be careful to check the `last_err_code` regularly.
pub fn clear(&mut self) {
unsafe {
// NOTE: previous nixops4 used the line: (maybe for compatability with old versions?)
// sys::nix_set_err_msg(self.inner.as_ptr(), sys::nix_err_NIX_OK, c"".as_ptr());
sys::nix_clear_err(self.as_ptr());
}
}
/// Returns [None] if [self.code] is [sys::nix_err_NIX_OK], and [Some] otherwise.
///
/// # Nix C++ API Internals
/// ```cpp
/// nix_err nix_err_code(const nix_c_context * read_context)
/// {
/// return read_context->last_err_code;
/// }
/// ```
/// This function **never fails**.
pub(super) fn get_err(&self) -> Option<sys::nix_err> {
let err = unsafe { sys::nix_err_code(self.as_ptr()) };
if err == sys::nix_err_NIX_OK {
None
} else {
Some(err)
}
}
/// Returns [None] if [self.code] is [sys::nix_err_NIX_OK], and [Some] otherwise.
///
/// # Nix C++ API Internals
/// ```cpp
/// const char * nix_err_msg(nix_c_context * context, const nix_c_context * read_context, unsigned int * n)
/// {
/// if (context)
/// context->last_err_code = NIX_OK;
/// if (read_context->last_err && read_context->last_err_code != NIX_OK) {
/// if (n)
/// *n = read_context->last_err->size();
/// return read_context->last_err->c_str();
/// }
/// nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message");
/// return nullptr;
/// }
/// ```
///
/// # Note
/// On failure [sys::nix_err_name] does the following if the error
/// has the error code [sys::nix_err_NIX_OK]:
/// ```cpp
/// nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message");
/// return nullptr;
/// ```
/// Hence we can just test whether the returned pointer is a `NULL` pointer,
/// and avoid passing in a [sys::nix_c_context] struct.
pub(super) fn get_msg(&self) -> Option<String> {
// XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic?
let ctx = ErrorContext::new();
unsafe {
// NOTE: an Err here only occurs when `self.get_code() == Ok(())`
let mut n: c_uint = 0;
sys::nix_err_msg(ctx.as_ptr(), self.as_ptr(), &mut n)
.to_utf8_string()
.ok()
}
}
/// Returns [None] if [self.code] is [sys::nix_err_NIX_OK], and [Some] otherwise.
///
/// # Nix C++ API Internals
///
/// ```cpp
/// // NOTE(nixide): the implementation of `nix_err_info_msg` is identical to `nix_err_name`
/// nix_err nix_err_info_msg(
/// nix_c_context * context,
/// const nix_c_context * read_context,
/// nix_get_string_callback callback,
/// void * user_data)
/// {
/// if (context)
/// context->last_err_code = NIX_OK;
/// if (read_context->last_err_code != NIX_ERR_NIX_ERROR) {
/// // NOTE(nixide): `nix_set_err_msg` throws a `nix::Error` exception if `context == nullptr`
/// return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Last error was not a nix error");
/// }
/// // NOTE(nixide): `call_nix_get_string_callback` always returns `NIX_OK`
/// return call_nix_get_string_callback(read_context->info->msg.str(), callback, user_data);
/// }
/// ```
///
/// `nix_err_info_msg` accepts two `nix_c_context*`:
/// * `nix_c_context* context` - errors from the function call are logged here
/// * `const nix_c_context* read_context` - the context to read `info_msg` from
///
/// `nix_set_err_msg` will cause undefined behaviour if `context` is a null pointer (see below)
/// due to [https://github.com/rust-lang/rust-bindgen/issues/1208].
/// So we should never assigned it [std::ptr::null_mut].
/// ```cpp
/// if (context == nullptr) {
/// throw nix::Error("Nix C api error: %s", msg);
/// }
/// ```
pub(super) fn get_nix_err_name(&self) -> Option<String> {
// XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic?
unsafe {
// NOTE: an Err here only occurs when "Last error was not a nix error"
wrap_libnix_string_callback(|ctx, callback, user_data| {
sys::nix_err_name(ctx.as_ptr(), self.as_ptr(), Some(callback), user_data)
})
.ok()
}
}
/// Returns [None] if [self.code] is [sys::nix_err_NIX_OK], and [Some] otherwise.
///
/// # Nix C++ API Internals
///
/// ```cpp
/// // NOTE(nixide): the implementation of `nix_err_info_msg` is identical to `nix_err_name`
/// nix_err nix_err_info_msg(
/// nix_c_context * context,
/// const nix_c_context * read_context,
/// nix_get_string_callback callback,
/// void * user_data)
/// {
/// if (context)
/// context->last_err_code = NIX_OK;
/// if (read_context->last_err_code != NIX_ERR_NIX_ERROR) {
/// // NOTE(nixide): `nix_set_err_msg` throws a `nix::Error` exception if `context == nullptr`
/// return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Last error was not a nix error");
/// }
/// // NOTE(nixide): `call_nix_get_string_callback` always returns `NIX_OK`
/// return call_nix_get_string_callback(read_context->info->msg.str(), callback, user_data);
/// }
/// ```
///
/// `nix_err_info_msg` accepts two `nix_c_context*`:
/// * `nix_c_context* context` - errors from the function call are logged here
/// * `const nix_c_context* read_context` - the context to read `info_msg` from
///
/// `nix_set_err_msg` will cause undefined behaviour if `context` is a null pointer (see below)
/// due to [https://github.com/rust-lang/rust-bindgen/issues/1208].
/// So we should never assigned it [std::ptr::null_mut].
/// ```cpp
/// if (context == nullptr) {
/// throw nix::Error("Nix C api error: %s", msg);
/// }
/// ```
pub(super) fn get_nix_err_info_msg(&self) -> Option<String> {
// XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic?
unsafe {
// NOTE: an Err here only occurs when "Last error was not a nix error"
wrap_libnix_string_callback(|ctx, callback, user_data| {
sys::nix_err_info_msg(ctx.as_ptr(), self.as_ptr(), Some(callback), user_data)
})
.ok()
}
}
}
impl Drop for ErrorContext {
fn drop(&mut self) {
// SAFETY: We own the context and it's valid until drop
unsafe {
sys::nix_c_context_free(self.inner.as_ptr());
}
}
}

175
nixide/src/errors/error.rs Normal file
View file

@ -0,0 +1,175 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use super::{ErrorContext, NixError};
use crate::sys;
use crate::util::panic_issue_call_failed;
pub type NixideResult<T> = Result<T, NixideError>;
#[derive(Debug, Clone)]
pub enum NixideError {
/// # Warning
/// [NixideErrorVariant::NixError] is **not the same** as [sys::nix_err_NIX_ERR_NIX_ERROR],
/// that is instead mapped to [NixError::ExprEval]
NixError {
trace: String,
inner: sys::nix_err,
err: NixError,
msg: String,
},
/// Returned if a C string `*const c_char` contained a `\0` byte prematurely.
StringNulByte { trace: String },
/// Returned if a C string is not encoded in UTF-8.
StringNotUtf8 { trace: String },
/// Equivalent to the standard [std::ffi::NulError] type.
NullPtr { trace: String },
/// Invalid Argument
InvalidArg {
trace: String,
name: &'static str,
reason: String,
},
/// Invalid Type
InvalidType {
trace: String,
expected: &'static str,
got: String,
},
}
macro_rules! new_nixide_error {
(NixError, $inner:expr, $err:expr, $msg:expr) => {{
NixideError::NixError {
trace: stdext::debug_name!(),
inner: $inner,
err: $err,
msg: $msg,
}
}};
(StringNulByte) => {{
NixideError::StringNulByte {
trace: stdext::debug_name!(),
}
}};
(StringNotUtf8) => {{
NixideError::StringNotUtf8 {
trace: stdext::debug_name!(),
}
}};
(NullPtr) => {{
NixideError::NullPtr {
trace: stdext::debug_name!(),
}
}};
(InvalidArg, $name:expr, $reason:expr) => {{
NixideError::InvalidArg {
trace: stdext::debug_name!(),
name: $name,
reason: $reason,
}
}};
(InvalidType, $expected:expr, $got:expr) => {{
NixideError::InvalidType {
trace: stdext::debug_name!(),
expected: $expected,
got: $got,
}
}};
}
pub(crate) use new_nixide_error;
macro_rules! retrace_nixide_error {
($x:expr) => {{
new_nixide_error!($x.err)
}};
}
pub(crate) use retrace_nixide_error;
impl NixideError {
/// # Panics
///
/// This function will panic in the event that `context.get_err() == Some(err) && err == sys::nix_err_NIX_OK`
/// since `nixide::ErrorContext::get_err` is expected to return `None` to indicate `sys::ni_err_NIX_OK`.
///
///
/// This function will panic in the event that `value != sys::nix_err_NIX_OK`
/// but that `context.get_code() == sys::nix_err_NIX_OK`
pub(super) fn from_error_context(context: &ErrorContext) -> Option<NixideError> {
let inner = context.get_err()?;
let msg = context.get_msg()?;
#[allow(nonstandard_style)]
let err = match inner {
sys::nix_err_NIX_OK => unreachable!(),
sys::nix_err_NIX_ERR_OVERFLOW => NixError::Overflow,
sys::nix_err_NIX_ERR_KEY => NixError::KeyNotFound(None),
sys::nix_err_NIX_ERR_NIX_ERROR => NixError::ExprEval {
name: context
.get_nix_err_name()
.unwrap_or_else(|| panic_issue_call_failed!()),
info_msg: context
.get_nix_err_info_msg()
.unwrap_or_else(|| panic_issue_call_failed!()),
},
sys::nix_err_NIX_ERR_UNKNOWN => NixError::Unknown,
err => NixError::Undocumented(err),
};
Some(new_nixide_error!(NixError, inner, err, msg))
}
}
impl std::error::Error for NixideError {}
impl Display for NixideError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
NixideError::NixError {
trace,
inner,
err,
msg,
} => {
write!(f, "[nixide ~ {trace}]{err} (nix_err={inner}): {msg}")
}
NixideError::StringNulByte { trace } => {
write!(f, "[nixide ~ {trace}] Got premature `\\0` (NUL) byte")
}
NixideError::StringNotUtf8 { trace } => {
write!(f, "[nixide ~ {trace}] Expected UTF-8 encoded string")
}
NixideError::NullPtr { trace } => write!(f, "[nixide ~ {trace}] Got null pointer"),
NixideError::InvalidArg {
trace,
name,
reason,
} => {
write!(
f,
"[nixide ~ {trace}] Invalid argument `{name}`: reason \"{reason}\""
)
}
NixideError::InvalidType {
trace,
expected,
got,
} => write!(
f,
"[nixide ~ {trace}] Got invalid type: expected `{expected}` but got `{got}`"
),
}
}
}

9
nixide/src/errors/mod.rs Normal file
View file

@ -0,0 +1,9 @@
#[macro_use]
mod error;
mod context;
mod nix_error;
pub(crate) use context::ErrorContext;
pub(crate) use error::{new_nixide_error, retrace_nixide_error};
pub use error::{NixideError, NixideResult};
pub use nix_error::NixError;

View file

@ -0,0 +1,145 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::sys;
/// Standard (nix_err) and some additional error codes
/// produced by the libnix C API.
#[derive(Debug, Clone)]
pub enum NixError {
/// A generic Nix error occurred.
///
/// # Reason
///
/// This error code is returned when a generic Nix error occurs
/// during nixexpr evaluation.
///
/// # Nix C++ API Internals
///
/// ```cpp
/// // `NIX_ERR_NIX_ERROR` variant of the `nix_err` enum type
/// NIX_ERR_NIX_ERROR = -4
/// ```
ExprEval { name: String, info_msg: String },
/// A key/index access error occurred in C API functions.
///
/// # Reason
///
/// This error code is returned when accessing a key, index, or identifier that
/// does not exist in C API functions. Common scenarios include:
/// - Setting keys that don't exist (nix_setting_get, nix_setting_set)
/// - List indices that are out of bounds (nix_get_list_byidx*)
/// - Attribute names that don't exist (nix_get_attr_byname*)
/// - Attribute indices that are out of bounds (nix_get_attr_byidx*, nix_get_attr_name_byidx)
///
/// This error typically indicates incorrect usage or assumptions about data structure
/// contents, rather than internal Nix evaluation errors.
///
/// # Note
///
/// This error code should ONLY be returned by C API functions themselves,
/// not by underlying Nix evaluation. For example, evaluating `{}.foo` in Nix
/// will throw a normal error (NIX_ERR_NIX_ERROR), not NIX_ERR_KEY.
///
/// # Nix C++ API Internals
///
/// ```cpp
/// // `NIX_ERR_KEY` variant of the `nix_err` enum type
/// NIX_ERR_KEY = -3
/// ```
KeyNotFound(Option<String>),
/// An overflow error occurred.
///
/// # Reason
///
/// This error code is returned when an overflow error occurred during the
/// function execution.
///
/// # Nix C++ API Internals
///
/// ```cpp
/// // `NIX_ERR_OVERFLOW` variant of the `nix_err` enum type
/// NIX_ERR_OVERFLOW = -2
/// ```
Overflow,
/// An unknown error occurred.
///
/// # Reason
///
/// This error code is returned when an unknown error occurred during the
/// function execution.
///
/// # Nix C++ API Internals
///
/// ```cpp
/// // `NIX_ERR_OVERFLOW` variant of the `nix_err` enum type
/// NIX_ERR_UNKNOWN = -1
/// ```
Unknown,
///
/// An undocumented error occurred.
///
/// # Reason
///
/// The libnix C API defines `enum nix_err` as a signed integer value.
/// In the (unexpected) event libnix returns an error code with an
/// invalid enum value, or one I new addition I didn't know existed,
/// then an [NixError::Undocumented] is considered to have occurred.
///
/// # Nix C++ API Internals
///
/// [NixError::Undocumented] has no equivalent in the `libnix` api.
/// This is solely a language difference between C++ and Rust, since
/// [sys::nix_err] is defined over the *"continuous" (not realy)*
/// type [std::os::raw::c_int].
Undocumented(sys::nix_err),
}
// impl NixError {
// /// # Panics
// ///
// /// This function will panic in the event that `context.get_err() == Some(err) && err == sys::nix_err_NIX_OK`
// /// since `nixide::ErrorContext::get_err` is expected to return `None` to indicate `sys::ni_err_NIX_OK`.
// ///
// ///
// /// This function will panic in the event that `value != sys::nix_err_NIX_OK`
// /// but that `context.get_code() == sys::nix_err_NIX_OK`
// pub(super) fn from_error_context(context: &ErrorContext) -> Option<NixError> {
// #[allow(nonstandard_style)]
// match context.get_err()? {
// sys::nix_err_NIX_OK => unreachable!("call to `nixide::ErrorContext::get_err@nixide::NixError::from_context` failed: please open an issue on https://github.com/cry128/nixide"),
// sys::nix_err_NIX_ERR_OVERFLOW => Some(NixError::Overflow),
// sys::nix_err_NIX_ERR_KEY => Some(NixError::KeyNotFound(None)),
// sys::nix_err_NIX_ERR_NIX_ERROR => Some(NixError::ExprEval {
// name: context
// .get_nix_err_name()
// .expect("call to `nixide::ErrorContext::get_nix_err_name@nixide::NixError::from_context` failed: please open an issue on https://github.com/cry128/nixide"),
// info_msg: context.get_nix_err_info_msg()
// .expect("call to `nixide::ErrorContext::get_nix_err_info_msg@nixide::NixError::from_context` failed: please open an issue on https://github.com/cry128/nixide"),
// }),
// sys::nix_err_NIX_ERR_UNKNOWN => Some(NixError::Unknown),
// err => Some(NixError::Undocumented(err)),
// }
// }
// }
impl Display for NixError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
NixError::ExprEval { name, info_msg } => write!(f, "[libnix] NixExpr evaluation failed [name=\"{name}\", info_msg=\"{info_msg}\"]"),
NixError::KeyNotFound(Some(key)) => write!(f, "[libnix] Key not found \"{key}\""),
NixError::KeyNotFound(None) => write!(f, "[libnix] Key not found"),
NixError::Overflow => write!(f, "[libnix] Overflow error"),
NixError::Unknown => write!(f, "[libnix] Unknown error"),
NixError::Undocumented(err) => write!(
f,
"[libnix] An undocumented nix_err({err}) [please open an issue on https://github.com/cry128/nixide]"
),
}
}
}

View file

@ -2,9 +2,13 @@ use std::ffi::CString;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::errors::new_nixide_error;
use super::Value;
use crate::errors::ErrorContext;
use crate::sys;
use crate::{ErrorContext, NixErrorCode, Store};
use crate::util::wrappers::AsInnerPtr;
use crate::{NixideError, Store};
/// Nix evaluation state for evaluating expressions.
///
@ -12,23 +16,22 @@ use crate::{ErrorContext, NixErrorCode, Store};
/// and creating values.
pub struct EvalState {
inner: NonNull<sys::EvalState>,
// XXX: TODO: is an `Arc<Store>` necessary or just a `Store`
#[allow(dead_code)]
store: Arc<Store>,
pub(super) context: Arc<ErrorContext>,
}
impl AsInnerPtr<sys::EvalState> for EvalState {
unsafe fn as_ptr(&self) -> *mut sys::EvalState {
self.inner.as_ptr()
}
}
impl EvalState {
/// Construct a new EvalState directly from its attributes
pub(super) fn new(
inner: NonNull<sys::EvalState>,
store: Arc<Store>,
context: Arc<ErrorContext>,
) -> Self {
Self {
inner,
store,
context,
}
pub(super) fn new(inner: NonNull<sys::EvalState>, store: Arc<Store>) -> Self {
Self { inner, store }
}
/// Evaluate a Nix expression from a string.
@ -41,41 +44,36 @@ impl EvalState {
/// # Errors
///
/// Returns an error if evaluation fails.
pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value<'_>, NixErrorCode> {
let expr_c =
NixErrorCode::from_nulerror(CString::new(expr), "nixide::EvalState::eval_from_string")?;
let path_c =
NixErrorCode::from_nulerror(CString::new(path), "nixide::EvalState::eval_from_string")?;
pub fn eval_from_string(&self, expr: &str, path: &str) -> Result<Value, NixideError> {
let expr_c = CString::new(expr).or(Err(new_nixide_error!(StringNulByte)))?;
let path_c = CString::new(path).or(Err(new_nixide_error!(StringNulByte)))?;
let ctx = ErrorContext::new();
// Allocate value for result
// SAFETY: context and state are valid
let value_ptr = unsafe { sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr()) };
if value_ptr.is_null() {
return Err(NixErrorCode::NullPtr {
location: "nix_alloc_value",
});
}
// XXX: TODO: refactor this code to use `nixide::Value`
let value_ptr = unsafe { sys::nix_alloc_value(ctx.as_ptr(), self.as_ptr()) };
let value = match ctx.peak() {
Some(err) => Err(err),
None => match NonNull::new(value_ptr) {
Some(inner) => Ok(Value { inner }),
None => Err(new_nixide_error!(NullPtr)),
},
}?;
// Evaluate expression
// SAFETY: all pointers are valid
NixErrorCode::from(
unsafe {
sys::nix_expr_eval_from_string(
self.context.as_ptr(),
self.inner.as_ptr(),
expr_c.as_ptr(),
path_c.as_ptr(),
value_ptr,
)
},
"nix_expr_eval_from_string",
)?;
let inner = NonNull::new(value_ptr).ok_or(NixErrorCode::NullPtr {
location: "nix_expr_eval_from_string",
})?;
Ok(Value { inner, state: self })
unsafe {
sys::nix_expr_eval_from_string(
ctx.as_ptr(),
self.as_ptr(),
expr_c.as_ptr(),
path_c.as_ptr(),
value.as_ptr(),
);
};
match ctx.peak() {
Some(err) => Err(err),
None => Ok(value),
}
}
/// Allocate a new value.
@ -83,23 +81,16 @@ impl EvalState {
/// # Errors
///
/// Returns an error if value allocation fails.
pub fn alloc_value(&self) -> Result<Value<'_>, NixErrorCode> {
// SAFETY: context and state are valid
let value_ptr = unsafe { sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr()) };
let inner = NonNull::new(value_ptr).ok_or(NixErrorCode::NullPtr {
location: "nix_alloc_value",
})?;
Ok(Value { inner, state: self })
}
/// Get the raw state pointer.
///
/// # Safety
///
/// The caller must ensure the pointer is used safely.
pub(super) unsafe fn as_ptr(&self) -> *mut sys::EvalState {
self.inner.as_ptr()
pub fn alloc_value(&self) -> Result<Value, NixideError> {
let ctx = ErrorContext::new();
let value_ptr = unsafe { sys::nix_alloc_value(ctx.as_ptr(), self.as_ptr()) };
match ctx.peak() {
Some(err) => Err(err),
None => match NonNull::new(value_ptr) {
Some(inner) => Ok(Value { inner }),
None => Err(new_nixide_error!(NullPtr)),
},
}
}
}

View file

@ -2,8 +2,10 @@ use std::ptr::NonNull;
use std::sync::Arc;
use super::EvalState;
use crate::errors::{new_nixide_error, ErrorContext, NixideError};
use crate::sys;
use crate::{ErrorContext, NixErrorCode, Store};
use crate::util::wrappers::AsInnerPtr;
use crate::Store;
/// Builder for Nix evaluation state.
///
@ -12,7 +14,6 @@ use crate::{ErrorContext, NixErrorCode, Store};
pub struct EvalStateBuilder {
inner: NonNull<sys::nix_eval_state_builder>,
store: Arc<Store>,
context: Arc<ErrorContext>,
}
impl EvalStateBuilder {
@ -25,19 +26,16 @@ impl EvalStateBuilder {
/// # Errors
///
/// Returns an error if the builder cannot be created.
pub fn new(store: &Arc<Store>) -> Result<Self, NixErrorCode> {
pub fn new(store: &Arc<Store>) -> Result<Self, NixideError> {
// SAFETY: store context and store are valid
let builder_ptr =
unsafe { sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr()) };
let inner = NonNull::new(builder_ptr).ok_or(NixErrorCode::NullPtr {
location: "nix_eval_state_builder_new",
})?;
let inner = NonNull::new(builder_ptr).ok_or(new_nixide_error!(NullPtr))?;
Ok(EvalStateBuilder {
inner,
store: Arc::clone(store),
context: Arc::clone(&store._context),
})
}
@ -46,29 +44,24 @@ impl EvalStateBuilder {
/// # Errors
///
/// Returns an error if the evaluation state cannot be built.
pub fn build(self) -> Result<EvalState, NixErrorCode> {
pub fn build(self) -> Result<EvalState, NixideError> {
let ctx = ErrorContext::new();
// Load configuration first
// SAFETY: context and builder are valid
NixErrorCode::from(
unsafe { sys::nix_eval_state_builder_load(self.context.as_ptr(), self.inner.as_ptr()) },
"nix_eval_state_builder_load",
)?;
unsafe { sys::nix_eval_state_builder_load(ctx.as_ptr(), self.as_ptr()) };
if let Some(err) = ctx.peak() {
return Err(err);
}
// Build the state
// SAFETY: context and builder are valid
let state_ptr =
unsafe { sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr()) };
let state_ptr = unsafe { sys::nix_eval_state_build(ctx.as_ptr(), self.as_ptr()) };
if let Some(err) = ctx.peak() {
return Err(err);
}
let inner = NonNull::new(state_ptr).ok_or(NixErrorCode::NullPtr {
location: "nix_eval_state_build",
})?;
let inner = NonNull::new(state_ptr).ok_or(new_nixide_error!(NullPtr))?;
// The builder is consumed here - its Drop will clean up
Ok(EvalState::new(
inner,
self.store.clone(),
self.context.clone(),
))
Ok(EvalState::new(inner, self.store.clone()))
}
pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_eval_state_builder {

View file

@ -3,19 +3,13 @@ use std::sync::Arc;
use serial_test::serial;
use super::{EvalStateBuilder, ValueType};
use crate::{ErrorContext, Store};
#[test]
#[serial]
fn test_context_creation() {
let _ctx = ErrorContext::new().expect("Failed to create context");
// Context should be dropped automatically
}
use crate::errors::ErrorContext;
use crate::Store;
#[test]
#[serial]
fn test_eval_state_builder() {
let ctx = Arc::new(ErrorContext::new().expect("Failed to create context"));
let ctx = Arc::new(ErrorContext::new());
let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
let _state = EvalStateBuilder::new(&store)
.expect("Failed to create builder")
@ -27,7 +21,7 @@ fn test_eval_state_builder() {
#[test]
#[serial]
fn test_simple_evaluation() {
let ctx = Arc::new(ErrorContext::new().expect("Failed to create context"));
let ctx = Arc::new(ErrorContext::new());
let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
let state = EvalStateBuilder::new(&store)
.expect("Failed to create builder")
@ -45,7 +39,7 @@ fn test_simple_evaluation() {
#[test]
#[serial]
fn test_value_types() {
let ctx = Arc::new(ErrorContext::new().expect("Failed to create context"));
let ctx = Arc::new(ErrorContext::new());
let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
let state = EvalStateBuilder::new(&store)
.expect("Failed to create builder")
@ -77,7 +71,7 @@ fn test_value_types() {
#[test]
#[serial]
fn test_value_formatting() {
let ctx = Arc::new(ErrorContext::new().expect("Failed to create context"));
let ctx = Arc::new(ErrorContext::new());
let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
let state = EvalStateBuilder::new(&store)
.expect("Failed to create builder")

View file

@ -2,19 +2,32 @@ use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ptr::NonNull;
use super::{EvalState, ValueType};
use crate::errors::{new_nixide_error, ErrorContext, NixideError};
use crate::sys;
use crate::NixErrorCode;
use crate::util::wrappers::{AsInnerPtr, FromC as _};
use crate::util::AsErr;
/// A Nix value
///
/// This represents any value in the Nix language, including primitives,
/// collections, and functions.
pub struct Value<'a> {
pub struct Value {
pub(crate) inner: NonNull<sys::nix_value>,
pub(crate) state: &'a EvalState,
}
impl Value<'_> {
impl AsInnerPtr<sys::nix_value> for Value {
unsafe fn as_ptr(&self) -> *mut sys::nix_value {
self.inner.as_ptr()
}
}
impl Value {
pub(crate) unsafe fn new(inner: *mut sys::Value) -> Self {
Value {
inner: NonNull::new(inner).unwrap(),
}
}
/// Force evaluation of this value.
///
/// If the value is a thunk, this will evaluate it to its final form.
@ -22,18 +35,12 @@ impl Value<'_> {
/// # Errors
///
/// Returns an error if evaluation fails.
pub fn force(&mut self) -> Result<(), NixErrorCode> {
NixErrorCode::from(
// SAFETY: context, state, and value are valid
unsafe {
sys::nix_value_force(
self.state.context.as_ptr(),
self.state.as_ptr(),
self.inner.as_ptr(),
)
},
"nix_value_force",
)
pub fn force(&mut self, state: &EvalState) -> Result<(), NixideError> {
// XXX: TODO: move force and force_deep to the EvalState
let ctx = ErrorContext::new();
unsafe { sys::nix_value_force(ctx.as_ptr(), state.as_ptr(), self.as_ptr()) };
ctx.peak().as_err()
}
/// Force deep evaluation of this value.
@ -43,26 +50,38 @@ impl Value<'_> {
/// # Errors
///
/// Returns an error if evaluation fails.
pub fn force_deep(&mut self) -> Result<(), NixErrorCode> {
NixErrorCode::from(
// SAFETY: context, state, and value are valid
unsafe {
sys::nix_value_force_deep(
self.state.context.as_ptr(),
self.state.as_ptr(),
self.inner.as_ptr(),
)
},
"nix_value_force_deep",
)
pub fn force_deep(&mut self, state: &EvalState) -> Result<(), NixideError> {
let ctx = ErrorContext::new();
unsafe { sys::nix_value_force_deep(ctx.as_ptr(), state.as_ptr(), self.as_ptr()) };
ctx.peak().as_err()
}
/// Get the type of this value.
#[must_use]
pub fn value_type(&self) -> ValueType {
// SAFETY: context and value are valid
let c_type = unsafe { sys::nix_get_type(self.state.context.as_ptr(), self.inner.as_ptr()) };
ValueType::from_c(c_type)
let ctx = ErrorContext::new();
let value_type =
unsafe { ValueType::from_c(sys::nix_get_type(ctx.as_ptr(), self.as_ptr())) };
// NOTE: an error here only occurs if `nix_get_type` catches an error,
// NOTE: which in turn only happens if the `sys::nix_value*` is a null pointer
// NOTE: or points to an uninitialised `nix_value` struct.
ctx.peak()
.as_err()
.unwrap_or_else(|_| panic!("TODO im sleepy rn"));
value_type
}
fn expect_type(&self, expected: ValueType) -> Result<(), NixideError> {
let got = self.value_type();
if got != expected {
return Err(new_nixide_error!(
InvalidType,
expected.to_string(),
got.to_string()
));
}
Ok(())
}
/// Convert this value to an integer.
@ -70,19 +89,15 @@ impl Value<'_> {
/// # Errors
///
/// Returns an error if the value is not an integer.
pub fn as_int(&self) -> Result<i64, NixErrorCode> {
if self.value_type() != ValueType::Int {
return Err(NixErrorCode::InvalidType {
location: "nixide::Value::as_int",
expected: "int",
got: self.value_type().to_string(),
});
pub fn as_int(&self) -> Result<i64, NixideError> {
self.expect_type(ValueType::Int)?;
let ctx = ErrorContext::new();
let result = unsafe { sys::nix_get_int(ctx.as_ptr(), self.as_ptr()) };
match ctx.peak() {
Some(err) => Err(err),
None => Ok(result),
}
// SAFETY: context and value are valid, type is checked
let result = unsafe { sys::nix_get_int(self.state.context.as_ptr(), self.inner.as_ptr()) };
Ok(result)
}
/// Convert this value to a float.
@ -90,20 +105,15 @@ impl Value<'_> {
/// # Errors
///
/// Returns an error if the value is not a float.
pub fn as_float(&self) -> Result<f64, NixErrorCode> {
if self.value_type() != ValueType::Float {
return Err(NixErrorCode::InvalidType {
location: "nixide::Value::as_float",
expected: "float",
got: self.value_type().to_string(),
});
pub fn as_float(&self) -> Result<f64, NixideError> {
self.expect_type(ValueType::Float)?;
let ctx = ErrorContext::new();
let result = unsafe { sys::nix_get_float(ctx.as_ptr(), self.as_ptr()) };
match ctx.peak() {
Some(err) => Err(err),
None => Ok(result),
}
// SAFETY: context and value are valid, type is checked
let result =
unsafe { sys::nix_get_float(self.state.context.as_ptr(), self.inner.as_ptr()) };
Ok(result)
}
/// Convert this value to a boolean.
@ -111,19 +121,15 @@ impl Value<'_> {
/// # Errors
///
/// Returns an error if the value is not a boolean.
pub fn as_bool(&self) -> Result<bool, NixErrorCode> {
if self.value_type() != ValueType::Bool {
return Err(NixErrorCode::InvalidType {
location: "nixide::Value::as_bool",
expected: "bool",
got: self.value_type().to_string(),
});
pub fn as_bool(&self) -> Result<bool, NixideError> {
self.expect_type(ValueType::Bool)?;
let ctx = ErrorContext::new();
let result = unsafe { sys::nix_get_bool(ctx.as_ptr(), self.as_ptr()) };
match ctx.peak() {
Some(err) => Err(err),
None => Ok(result),
}
// SAFETY: context and value are valid, type is checked
let result = unsafe { sys::nix_get_bool(self.state.context.as_ptr(), self.inner.as_ptr()) };
Ok(result)
}
/// Convert this value to a string.
@ -131,33 +137,25 @@ impl Value<'_> {
/// # Errors
///
/// Returns an error if the value is not a string.
pub fn as_string(&self) -> Result<String, NixErrorCode> {
if self.value_type() != ValueType::String {
return Err(NixErrorCode::InvalidType {
location: "nixide::Value::as_string",
expected: "string",
got: self.value_type().to_string(),
});
}
pub fn as_string(&self) -> Result<String, NixideError> {
self.expect_type(ValueType::String)?;
let ctx = ErrorContext::new();
// For string values, we need to use realised string API
// SAFETY: context and value are valid, type is checked
let realised_str = unsafe {
sys::nix_string_realise(
self.state.context.as_ptr(),
ctx.as_ptr(),
self.state.as_ptr(),
self.inner.as_ptr(),
self.as_ptr(),
false, // don't copy more
)
};
if realised_str.is_null() {
return Err(NixErrorCode::NullPtr {
location: "nix_string_realise",
});
return Err(new_nixide_error!(NullPtr));
}
// SAFETY: realised_str is non-null and points to valid RealizedString
let buffer_start = unsafe { sys::nix_realised_string_get_buffer_start(realised_str) };
let buffer_size = unsafe { sys::nix_realised_string_get_buffer_size(realised_str) };
if buffer_start.is_null() {
@ -165,18 +163,12 @@ impl Value<'_> {
unsafe {
sys::nix_realised_string_free(realised_str);
}
return Err(NixErrorCode::NullPtr {
location: "nix_realised_string_free",
});
return Err(new_nixide_error!(NullPtr));
}
// SAFETY: buffer_start is non-null and buffer_size gives us the length
let bytes = unsafe { std::slice::from_raw_parts(buffer_start.cast::<u8>(), buffer_size) };
let string = std::str::from_utf8(bytes)
.map_err(|_| NixErrorCode::Unknown {
location: "nixide::Value::as_string",
reason: "Invalid UTF-8 in string".to_string(),
})?
.map_err(|_| new_nixide_error!(StringNotUtf8))?
.to_owned();
// Clean up realised string
@ -187,16 +179,6 @@ impl Value<'_> {
Ok(string)
}
/// Get the raw value pointer.
///
/// # Safety
///
/// The caller must ensure the pointer is used safely.
#[allow(dead_code)]
unsafe fn as_ptr(&self) -> *mut sys::nix_value {
self.inner.as_ptr()
}
/// Format this value as Nix syntax.
///
/// This provides a string representation that matches Nix's own syntax,
@ -206,7 +188,7 @@ impl Value<'_> {
///
/// Returns an error if the value cannot be converted to a string
/// representation.
pub fn to_nix_string(&self) -> Result<String, NixErrorCode> {
pub fn to_nix_string(&self) -> Result<String, NixideError> {
match self.value_type() {
ValueType::Int => Ok(self.as_int()?.to_string()),
ValueType::Float => Ok(self.as_float()?.to_string()),
@ -227,16 +209,16 @@ impl Value<'_> {
}
}
impl Drop for Value<'_> {
impl Drop for Value {
fn drop(&mut self) {
// SAFETY: We own the value and it's valid until drop
let ctx = ErrorContext::new();
unsafe {
sys::nix_value_decref(self.state.context.as_ptr(), self.inner.as_ptr());
sys::nix_value_decref(ctx.as_ptr(), self.as_ptr());
}
}
}
impl Display for Value<'_> {
impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self.value_type() {
ValueType::Int => {
@ -278,7 +260,7 @@ impl Display for Value<'_> {
}
}
impl Debug for Value<'_> {
impl Debug for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let value_type = self.value_type();
match value_type {

View file

@ -1,6 +1,6 @@
use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::sys;
use crate::{sys, util::wrappers::FromC};
/// Nix value types.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -29,8 +29,8 @@ pub enum ValueType {
External,
}
impl ValueType {
pub(super) fn from_c(value_type: sys::ValueType) -> Self {
impl FromC<sys::ValueType> for ValueType {
unsafe fn from_c(value_type: sys::ValueType) -> Self {
match value_type {
sys::ValueType_NIX_TYPE_THUNK => ValueType::Thunk,
sys::ValueType_NIX_TYPE_INT => ValueType::Int,
@ -43,7 +43,7 @@ impl ValueType {
sys::ValueType_NIX_TYPE_LIST => ValueType::List,
sys::ValueType_NIX_TYPE_FUNCTION => ValueType::Function,
sys::ValueType_NIX_TYPE_EXTERNAL => ValueType::External,
_ => ValueType::Thunk, // fallback (TODO: is this ok?)
_ => unreachable!("call to `nixide::ValueType::from_c` failed: please open an issue on https://github.com/cry128/nixide"),
}
}
}

View file

@ -1,14 +1,14 @@
use super::FlakeSettings;
use crate::{EvalStateBuilder, NixErrorCode};
use crate::{EvalStateBuilder, NixideError};
pub trait EvalStateBuilderExt {
/// Configures the eval state to provide flakes features such as `builtins.getFlake`.
fn flakes(self, settings: &FlakeSettings) -> Result<EvalStateBuilder, NixErrorCode>;
fn flakes(self, settings: &FlakeSettings) -> Result<EvalStateBuilder, NixideError>;
}
impl EvalStateBuilderExt for EvalStateBuilder {
/// Configures the eval state to provide flakes features such as `builtins.getFlake`.
fn flakes(mut self, settings: &FlakeSettings) -> Result<EvalStateBuilder, NixErrorCode> {
fn flakes(mut self, settings: &FlakeSettings) -> Result<EvalStateBuilder, NixideError> {
settings.add_to_eval_state_builder(&mut self).map(|_| self)
}
}

View file

@ -1,20 +1,20 @@
use std::ptr::NonNull;
use crate::errors::{new_nixide_error, ErrorContext};
use crate::sys;
use crate::{ErrorContext, NixErrorCode};
use crate::util::wrappers::AsInnerPtr;
use crate::NixideError;
pub(super) struct FetchersSettings {
pub(super) ptr: NonNull<sys::nix_fetchers_settings>,
}
impl FetchersSettings {
pub fn new() -> Result<Self, NixErrorCode> {
let ctx = ErrorContext::new()?;
pub fn new() -> Result<Self, NixideError> {
let ctx = ErrorContext::new();
let ptr = unsafe { sys::nix_fetchers_settings_new(ctx.as_ptr()) };
Ok(FetchersSettings {
ptr: NonNull::new(ptr).ok_or(NixErrorCode::NullPtr {
location: "fetchers_settings_new",
})?,
ptr: NonNull::new(ptr).ok_or(new_nixide_error!(NullPtr))?,
})
}

View file

@ -2,8 +2,11 @@ use std::ffi::CString;
use std::ptr::NonNull;
use super::{FlakeReference, FlakeSettings};
use crate::errors::new_nixide_error;
use crate::errors::ErrorContext;
use crate::sys;
use crate::{ErrorContext, NixErrorCode};
use crate::util::wrappers::AsInnerPtr;
use crate::NixideError;
#[derive(Debug, Clone)]
pub enum FlakeLockMode {
@ -30,46 +33,40 @@ impl Drop for FlakeLockFlags {
}
impl FlakeLockFlags {
// XXX: TODO: what is the default FlakeLockMode?
pub fn new(settings: &FlakeSettings) -> Result<Self, NixErrorCode> {
ErrorContext::new().and_then(|ctx| {
NonNull::new(unsafe { sys::nix_flake_lock_flags_new(ctx.as_ptr(), settings.as_ptr()) })
.ok_or(NixErrorCode::NulError {
location: "nix_flake_lock_flags_new",
})
.map(|inner| FlakeLockFlags { inner })
})
pub fn new(settings: &FlakeSettings) -> Result<Self, NixideError> {
let ctx = ErrorContext::new();
NonNull::new(unsafe { sys::nix_flake_lock_flags_new(ctx.as_ptr(), settings.as_ptr()) })
.ok_or(new_nixide_error!(NullPtr))
.map(|inner| FlakeLockFlags { inner })
}
pub(crate) fn as_ptr(&self) -> *mut sys::nix_flake_lock_flags {
self.inner.as_ptr()
}
pub fn set_lock_mode(&mut self, mode: &FlakeLockMode) -> Result<(), NixErrorCode> {
ErrorContext::new().and_then(|ctx| unsafe {
NixErrorCode::from(
match mode {
FlakeLockMode::WriteAsNeeded => {
sys::nix_flake_lock_flags_set_mode_write_as_needed(
ctx.as_ptr(),
self.as_ptr(),
)
}
FlakeLockMode::Virtual => {
sys::nix_flake_lock_flags_set_mode_virtual(ctx.as_ptr(), self.as_ptr())
}
FlakeLockMode::Check => {
sys::nix_flake_lock_flags_set_mode_check(ctx.as_ptr(), self.as_ptr())
}
},
"nix_flake_lock_flags_set_mode_check",
)
})
pub fn set_lock_mode(&mut self, mode: &FlakeLockMode) -> Result<(), NixideError> {
let ctx = ErrorContext::new();
match mode {
FlakeLockMode::WriteAsNeeded => unsafe {
sys::nix_flake_lock_flags_set_mode_write_as_needed(ctx.as_ptr(), self.as_ptr())
},
FlakeLockMode::Virtual => unsafe {
sys::nix_flake_lock_flags_set_mode_virtual(ctx.as_ptr(), self.as_ptr())
},
FlakeLockMode::Check => unsafe {
sys::nix_flake_lock_flags_set_mode_check(ctx.as_ptr(), self.as_ptr())
},
};
match ctx.peak() {
Some(err) => Err(err),
None => Ok(()),
}
}
/// Configures [LockedFlake::lock] to make incremental changes to the lock file as needed. Changes are written to file.
// pub fn set_mode_write_as_needed(&mut self) -> Result<(), NixErrorCode> {
// pub fn set_mode_write_as_needed(&mut self) -> Result<(), NixideError> {
// ErrorContext::new().and_then(|ctx| {
// NixErrorCode::from(
// NixideError::from(
// unsafe {
// sys::nix_flake_lock_flags_set_mode_write_as_needed(ctx.as_ptr(), self.as_ptr())
// },
@ -79,9 +76,9 @@ impl FlakeLockFlags {
// }
/// Make [LockedFlake::lock] check if the lock file is up to date. If not, an error is returned.
// pub fn set_mode_check(&mut self) -> Result<(), NixErrorCode> {
// pub fn set_mode_check(&mut self) -> Result<(), NixideError> {
// ErrorContext::new().and_then(|ctx| {
// NixErrorCode::from(
// NixideError::from(
// unsafe { sys::nix_flake_lock_flags_set_mode_check(ctx.as_ptr(), self.as_ptr()) },
// "nix_flake_lock_flags_set_mode_check",
// )
@ -89,9 +86,9 @@ impl FlakeLockFlags {
// }
/// Like `set_mode_write_as_needed`, but does not write to the lock file.
// pub fn set_mode_virtual(&mut self) -> Result<(), NixErrorCode> {
// pub fn set_mode_virtual(&mut self) -> Result<(), NixideError> {
// ErrorContext::new().and_then(|ctx| {
// NixErrorCode::from(
// NixideError::from(
// unsafe { sys::nix_flake_lock_flags_set_mode_virtual(ctx.as_ptr(), self.as_ptr()) },
// "nix_flake_lock_flags_set_mode_virtual",
// )
@ -114,24 +111,21 @@ impl FlakeLockFlags {
&mut self,
path: &str,
flakeref: &FlakeReference,
) -> Result<(), NixErrorCode> {
let input_path = NixErrorCode::from_nulerror(
CString::new(path),
"nixide::FlakeLockArgs::override_input",
)?;
) -> Result<(), NixideError> {
let input_path = CString::new(path).or_else(|_| Err(new_nixide_error!(StringNulByte)));
ErrorContext::new().and_then(|ctx| unsafe {
NixErrorCode::from(
unsafe {
sys::nix_flake_lock_flags_add_input_override(
ctx.as_ptr(),
self.as_ptr(),
input_path.as_ptr(),
flakeref.as_ptr(),
)
},
"nix_flake_lock_flags_add_input_override",
let ctx = ErrorContext::new();
unsafe {
sys::nix_flake_lock_flags_add_input_override(
ctx.as_ptr(),
self.as_ptr(),
input_path.as_ptr(),
flakeref.as_ptr(),
)
})
};
match ctx.peak() {
Some(err) => Err(err),
None => Ok(()),
}
}
}

View file

@ -1,14 +1,30 @@
use std::os::raw::c_char;
use std::ptr::{null_mut, NonNull};
use super::{FetchersSettings, FlakeReferenceParseFlags, FlakeSettings};
use crate::errors::new_nixide_error;
use crate::sys;
use crate::util::bindings::wrap_libnix_string_callback;
use crate::util::wrappers::AsInnerPtr;
use crate::NixideError;
pub struct FlakeReference {
pub(crate) ptr: NonNull<raw::flake_reference>,
pub(crate) ptr: NonNull<sys::nix_flake_reference>,
}
impl Drop for FlakeReference {
fn drop(&mut self) {
unsafe {
raw::flake_reference_free(self.ptr.as_ptr());
sys::nix_flake_reference_free(self.ptr.as_ptr());
}
}
}
impl FlakeReference {
pub fn as_ptr(&self) -> *mut sys::nix_flake_reference {
self.ptr.as_ptr()
}
/// Parse a flake reference from a string.
/// The string must be a valid flake reference, such as `github:owner/repo`.
/// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`,
@ -18,26 +34,25 @@ impl FlakeReference {
flake_settings: &FlakeSettings,
flags: &FlakeReferenceParseFlags,
reference: &str,
) -> Result<(FlakeReference, String)> {
let mut ctx = Context::new();
let mut r = result_string_init!();
let mut ptr: *mut raw::flake_reference = std::ptr::null_mut();
unsafe {
context::check_call!(raw::flake_reference_and_fragment_from_string(
&mut ctx,
fetch_settings.raw_ptr(),
flake_settings.ptr,
flags.ptr.as_ptr(),
) -> Result<(FlakeReference, String), NixideError> {
let mut ptr: *mut sys::nix_flake_reference = null_mut();
let result = wrap_libnix_string_callback(|ctx, callback, user_data| unsafe {
sys::nix_flake_reference_and_fragment_from_string(
ctx.as_ptr(),
fetch_settings.as_ptr(),
flake_settings.as_ptr(),
flags.as_ptr(),
reference.as_ptr() as *const c_char,
reference.len(),
// pointer to ptr
&mut ptr,
Some(callback_get_result_string),
callback_get_result_string_data(&mut r)
))
}?;
let ptr = NonNull::new(ptr)
.context("flake_reference_and_fragment_from_string unexpectedly returned null")?;
Ok((FlakeReference { ptr }, r?))
Some(callback),
user_data,
)
});
match NonNull::new(ptr) {
Some(ptr) => result.map(|s| (FlakeReference { ptr }, s)),
None => Err(new_nixide_error!(NullPtr)),
}
}
}

View file

@ -1,13 +1,18 @@
use std::os::raw::c_char;
use std::ptr::NonNull;
use super::FlakeSettings;
use crate::errors::{new_nixide_error, ErrorContext};
use crate::sys;
use crate::{ErrorContext, NixErrorCode};
use crate::util::wrappers::AsInnerPtr;
use crate::NixideError;
/// Parameters for parsing a flake reference.
#[derive(Debug)]
pub struct FlakeReferenceParseFlags {
pub(crate) ptr: NonNull<sys::nix_flake_reference_parse_flags>,
}
impl Drop for FlakeReferenceParseFlags {
fn drop(&mut self) {
unsafe {
@ -15,31 +20,39 @@ impl Drop for FlakeReferenceParseFlags {
}
}
}
impl FlakeReferenceParseFlags {
pub fn new(settings: &FlakeSettings) -> Result<Self, NixErrorCode> {
let mut ctx = ErrorContext::new();
let ptr = unsafe {
context::check_call!(sys::nix_flake_reference_parse_flags_new(
&mut ctx,
settings.ptr
))
}?;
let ptr = NonNull::new(ptr)
.context("flake_reference_parse_flags_new unexpectedly returned null")?;
Ok(FlakeReferenceParseFlags { ptr })
pub fn new(settings: &FlakeSettings) -> Result<Self, NixideError> {
let ctx = ErrorContext::new();
let ptr =
unsafe { sys::nix_flake_reference_parse_flags_new(ctx.as_ptr(), settings.as_ptr()) };
match ctx.peak() {
Some(err) => Err(err),
None => NonNull::new(ptr).map_or(Err(new_nixide_error!(NullPtr)), |ptr| {
Ok(FlakeReferenceParseFlags { ptr })
}),
}
}
/// Sets the [base directory](https://nix.dev/manual/nix/latest/glossary#gloss-base-directory)
/// for resolving local flake references.
pub fn set_base_directory(&mut self, base_directory: &str) -> Result<(), NixErrorCode> {
let mut ctx = ErrorContext::new();
pub fn set_base_directory(&mut self, base_directory: &str) -> Result<(), NixideError> {
let ctx = ErrorContext::new();
unsafe {
sys::context::check_call!(sys::nix_flake_reference_parse_flags_set_base_directory(
&mut ctx,
self.ptr.as_ptr(),
sys::nix_flake_reference_parse_flags_set_base_directory(
ctx.as_ptr(),
self.as_ptr(),
base_directory.as_ptr() as *const c_char,
base_directory.len()
))
}?;
Ok(())
base_directory.len(),
)
};
match ctx.peak() {
Some(err) => Err(err),
None => Ok(()),
}
}
pub fn as_ptr(&self) -> *mut sys::nix_flake_reference_parse_flags {
self.ptr.as_ptr()
}
}

View file

@ -1,45 +1,51 @@
use std::ptr::NonNull;
use crate::errors::{new_nixide_error, ErrorContext};
use crate::sys;
use crate::{ErrorContext, EvalStateBuilder, NixErrorCode};
use crate::util::wrappers::AsInnerPtr;
use crate::{EvalStateBuilder, NixideError};
/// Store settings for the flakes feature.
pub struct FlakeSettings {
pub(crate) inner: NonNull<sys::nix_flake_settings>,
}
impl AsInnerPtr<sys::nix_flake_settings> for FlakeSettings {
unsafe fn as_ptr(&self) -> *mut sys::nix_flake_settings {
self.inner.as_ptr()
}
}
impl FlakeSettings {
pub fn new() -> Result<Self, NixErrorCode> {
let ctx = ErrorContext::new()?;
let inner = NonNull::new(unsafe { sys::nix_flake_settings_new(ctx.as_ptr()) }).ok_or(
NixErrorCode::NullPtr {
location: "nix_flake_settings_new",
pub fn new() -> Result<Self, NixideError> {
let ctx = ErrorContext::new();
let opt = NonNull::new(unsafe { sys::nix_flake_settings_new(ctx.as_ptr()) });
match ctx.peak() {
Some(err) => Err(err),
None => match opt {
Some(inner) => Ok(FlakeSettings { inner }),
None => Err(new_nixide_error!(NullPtr)),
},
)?;
Ok(FlakeSettings { inner })
}
}
pub(super) fn add_to_eval_state_builder(
&self,
builder: &mut EvalStateBuilder,
) -> Result<(), NixErrorCode> {
let ctx = ErrorContext::new()?;
NixErrorCode::from(
unsafe {
sys::nix_flake_settings_add_to_eval_state_builder(
ctx.as_ptr(),
self.as_ptr(),
builder.as_ptr(),
)
},
"nix_flake_settings_add_to_eval_state_builder",
)?;
Ok(())
}
pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_flake_settings {
self.inner.as_ptr()
) -> Result<(), NixideError> {
let ctx = ErrorContext::new();
unsafe {
sys::nix_flake_settings_add_to_eval_state_builder(
ctx.as_ptr(),
self.as_ptr(),
builder.as_ptr(),
)
};
match ctx.peak() {
Some(err) => Err(err),
None => Ok(()),
}
}
}

View file

@ -1,10 +1,18 @@
use std::ptr::NonNull;
use super::{FetchersSettings, FlakeLockFlags, FlakeReference, FlakeSettings};
use crate::errors::{new_nixide_error, ErrorContext};
use crate::sys;
use crate::util::wrappers::AsInnerPtr;
use crate::{EvalState, NixideError, Value};
pub struct LockedFlake {
pub(crate) ptr: NonNull<raw::locked_flake>,
pub(crate) ptr: NonNull<sys::nix_locked_flake>,
}
impl Drop for LockedFlake {
fn drop(&mut self) {
unsafe {
raw::locked_flake_free(self.ptr.as_ptr());
sys::nix_locked_flake_free(self.ptr.as_ptr());
}
}
}
@ -15,20 +23,24 @@ impl LockedFlake {
eval_state: &EvalState,
flags: &FlakeLockFlags,
flake_ref: &FlakeReference,
) -> Result<LockedFlake> {
let mut ctx = Context::new();
let ptr = unsafe {
context::check_call!(raw::flake_lock(
&mut ctx,
fetch_settings.raw_ptr(),
flake_settings.ptr,
eval_state.raw_ptr(),
flags.ptr,
flake_ref.ptr.as_ptr()
))
}?;
let ptr = NonNull::new(ptr).context("flake_lock unexpectedly returned null")?;
Ok(LockedFlake { ptr })
) -> Result<LockedFlake, NixideError> {
let ctx = ErrorContext::new();
let ptr = NonNull::new(unsafe {
sys::nix_flake_lock(
ctx.as_ptr(),
fetch_settings.as_ptr(),
flake_settings.as_ptr(),
eval_state.as_ptr(),
flags.as_ptr(),
flake_ref.as_ptr(),
)
});
match ptr {
Some(ptr) => Ok(LockedFlake { ptr }),
None => Err(new_nixide_error!(NullPtr)),
}
}
/// Returns the outputs of the flake - the result of calling the `outputs` attribute.
@ -36,24 +48,27 @@ impl LockedFlake {
&self,
flake_settings: &FlakeSettings,
eval_state: &mut EvalState,
) -> Result<nix_bindings_expr::value::Value> {
let mut ctx = Context::new();
unsafe {
let r = context::check_call!(raw::locked_flake_get_output_attrs(
&mut ctx,
flake_settings.ptr,
eval_state.raw_ptr(),
self.ptr.as_ptr()
))?;
Ok(nix_bindings_expr::value::__private::raw_value_new(r))
}
) -> Result<Value, NixideError> {
let ctx = ErrorContext::new();
let r = unsafe {
sys::nix_locked_flake_get_output_attrs(
ctx.as_ptr(),
flake_settings.as_ptr(),
eval_state.as_ptr(),
self.ptr.as_ptr(),
)
};
Ok(nix_bindings_expr::value::__private::raw_value_new(r))
}
}
#[cfg(test)]
mod tests {
use nix_bindings_expr::eval_state::{gc_register_my_thread, EvalStateBuilder};
use nix_bindings_store::store::Store;
// use nix_bindings_expr::eval_state::{gc_register_my_thread, EvalStateBuilder};
use crate::flake::{FlakeLockMode, FlakeReferenceParseFlags};
use crate::{EvalStateBuilder, Store};
use super::*;
use std::sync::Once;
@ -248,7 +263,9 @@ mod tests {
// Step 1: Do not update (check), fails
flake_lock_flags.set_mode_check().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::Check)
.unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,
@ -265,7 +282,9 @@ mod tests {
};
// Step 2: Update but do not write, succeeds
flake_lock_flags.set_mode_virtual().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::Virtual)
.unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,
@ -287,7 +306,9 @@ mod tests {
// Step 3: The lock was not written, so Step 1 would fail again
flake_lock_flags.set_mode_check().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::Check)
.unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,
@ -307,7 +328,9 @@ mod tests {
// Step 4: Update and write, succeeds
flake_lock_flags.set_mode_write_as_needed().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::WriteAsNeeded)
.unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,
@ -327,7 +350,9 @@ mod tests {
// Step 5: Lock was written, so Step 1 succeeds
flake_lock_flags.set_mode_check().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::Check)
.unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,
@ -348,7 +373,9 @@ mod tests {
// Step 6: Lock with override, do not write
// This shouldn't matter; write_as_needed will be overridden
flake_lock_flags.set_mode_write_as_needed().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::WriteAsNeeded)
.unwrap();
let (flake_ref_c, fragment) = FlakeReference::parse_with_fragment(
&fetchers_settings,
@ -359,9 +386,7 @@ mod tests {
.unwrap();
assert_eq!(fragment, "");
flake_lock_flags
.add_input_override("b", &flake_ref_c)
.unwrap();
flake_lock_flags.override_input("b", &flake_ref_c).unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,
@ -384,7 +409,9 @@ mod tests {
// Step 7: Override was not written; lock still points to b
flake_lock_flags.set_mode_check().unwrap();
flake_lock_flags
.set_lock_mode(&FlakeLockMode::Check)
.unwrap();
let locked_flake = LockedFlake::lock(
&fetchers_settings,

View file

@ -8,7 +8,7 @@ mod locked_flake;
pub(self) use eval_state_builder_ext::EvalStateBuilderExt;
pub(self) use fetchers_settings::FetchersSettings;
pub(self) use flake_lock_flags::FlakeLockFlags;
pub(self) use flake_lock_flags::{FlakeLockFlags, FlakeLockMode};
pub(self) use flake_reference::FlakeReference;
pub(self) use flake_reference_parse_flags::FlakeReferenceParseFlags;
pub(self) use flake_settings::FlakeSettings;

View file

@ -1,16 +1,14 @@
// #![warn(missing_docs)]
mod context;
mod error;
pub(crate) mod errors;
mod expr;
mod flake;
mod store;
pub mod util;
pub(crate) mod util;
mod verbosity;
mod version;
pub use context::ErrorContext;
pub use error::NixErrorCode;
pub use errors::{NixError, NixideError, NixideResult};
pub use expr::{EvalState, EvalStateBuilder, Value, ValueType};
pub use store::{Store, StorePath};
pub use verbosity::NixVerbosity;

View file

@ -161,7 +161,7 @@ impl Store {
)
};
NixErrorCode::from(err, "nix_store_realise")?;
NixErrorCode::result_from(err, "nix_store_realise")?;
// Return the collected outputs
Ok(userdata.0)
@ -245,7 +245,7 @@ impl Store {
store_path.inner.as_ptr(),
)
};
NixErrorCode::from(err, "nix_store_copy_closure")
NixErrorCode::result_from(err, "nix_store_copy_closure")
}
pub fn copy_closure_from(
@ -261,7 +261,7 @@ impl Store {
store_path.inner.as_ptr(),
)
};
NixErrorCode::from(err, "nix_store_copy_closure")
NixErrorCode::result_from(err, "nix_store_copy_closure")
}
}

View file

@ -4,8 +4,11 @@ use std::ptr::NonNull;
use std::sync::Arc;
use super::Store;
use crate::errors::{new_nixide_error, ErrorContext};
use crate::util::bindings::{wrap_libnix_pathbuf_callback, wrap_libnix_string_callback};
use crate::{ErrorContext, NixErrorCode};
use crate::util::wrappers::AsInnerPtr;
use crate::NixideError;
use nixide_sys::{self as sys, nix_err_NIX_OK};
/// A path in the Nix store.
@ -13,7 +16,6 @@ use nixide_sys::{self as sys, nix_err_NIX_OK};
/// Represents a store path that can be realized, queried, or manipulated.
pub struct StorePath {
pub(crate) inner: NonNull<sys::StorePath>,
pub(crate) _context: Arc<ErrorContext>,
}
impl StorePath {
@ -21,36 +23,31 @@ impl StorePath {
///
/// # Arguments
///
/// * `context` - The Nix context
/// * `store` - The store containing the path
/// * `path` - The store path string (e.g., "/nix/store/...")
///
/// # Errors
///
/// Returns an error if the path cannot be parsed.
pub fn parse(
context: &Arc<ErrorContext>,
store: &Store,
path: &str,
) -> Result<Self, NixErrorCode> {
let path_cstring = CString::new(path).or(Err(NixErrorCode::InvalidArg {
location: "nixide::StorePath::parse",
reason: "`path` contains NUL char",
}))?;
pub fn parse(store: &Store, path: &str) -> Result<Self, NixideError> {
let path_cstring = CString::new(path).or(Err(new_nixide_error!(
InvalidArg,
"path",
"contains a `\\0` (NUL) byte".to_owned()
)))?;
// SAFETY: context, store, and path_cstring are valid
let ctx = ErrorContext::new();
let path_ptr = unsafe {
sys::nix_store_parse_path(context.as_ptr(), store.as_ptr(), path_cstring.as_ptr())
sys::nix_store_parse_path(ctx.as_ptr(), store.as_ptr(), path_cstring.as_ptr())
};
let inner = NonNull::new(path_ptr).ok_or(NixErrorCode::NullPtr {
location: "nix_store_parse_path",
})?;
Ok(Self {
inner,
_context: Arc::clone(context),
})
match ctx.peak() {
Some(err) => Err(err),
None => match NonNull::new(path_ptr) {
Some(inner) => Ok(Self { inner }),
None => Err(new_nixide_error!(NullPtr)),
},
}
}
/// Get the name component of the store path.
@ -61,8 +58,9 @@ impl StorePath {
/// # Errors
///
/// Returns an error if the name cannot be retrieved.
pub fn name(&self) -> Result<String, NixErrorCode> {
wrap_libnix_string_callback("nix_store_path_name", |callback, user_data| unsafe {
///
pub fn name(&self) -> Result<String, NixideError> {
wrap_libnix_string_callback(|_, callback, user_data| unsafe {
sys::nix_store_path_name(self.inner.as_ptr(), Some(callback), user_data);
// NOTE: nix_store_path_name doesn't return nix_err, so we force it to return successfully
@ -91,15 +89,21 @@ impl StorePath {
///
/// * `store` - The store containing the path
///
pub fn real_path(&self, store: &Store) -> Result<PathBuf, NixErrorCode> {
wrap_libnix_pathbuf_callback("nix_store_real_path", |callback, user_data| unsafe {
sys::nix_store_real_path(
self._context.as_ptr(),
pub fn real_path(&self, store: &Store) -> Result<PathBuf, NixideError> {
wrap_libnix_pathbuf_callback(|ctx, callback, user_data| unsafe {
let err_code = sys::nix_store_real_path(
ctx.as_ptr(),
store.inner.as_ptr(),
self.inner.as_ptr(),
self.as_ptr(),
Some(callback),
user_data,
)
);
match ctx.pop() {
Some(err) => Err(err),
None => Ok(()),
}
err_code
})
}
@ -111,13 +115,12 @@ impl StorePath {
/// * `store` - The store containing the path
///
pub fn is_valid(&self, store: &Store) -> bool {
let ctx = ErrorContext::new();
unsafe {
sys::nix_store_is_valid_path(
self._context.as_ptr(),
store.inner.as_ptr(),
self.inner.as_ptr(),
)
sys::nix_store_is_valid_path(ctx.as_ptr(), store.inner.as_ptr(), self.inner.as_ptr())
}
ctx
}
/// Get the raw store path pointer.

View file

@ -1,40 +1,47 @@
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_uint, c_void};
use std::path::PathBuf;
use crate::NixErrorCode;
use crate::errors::{ErrorContext, NixideError};
use crate::util::CCharPtrNixExt;
pub fn wrap_libnix_string_callback<F>(
name: &'static str,
callback: F,
) -> Result<String, NixErrorCode>
pub fn wrap_libnix_string_callback<F>(callback: F) -> Result<String, NixideError>
where
F: FnOnce(unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), *mut c_void) -> i32,
F: FnOnce(
&ErrorContext,
unsafe extern "C" fn(*const c_char, c_uint, *mut c_void),
*mut c_void,
) -> i32,
{
// Callback to receive the string
unsafe extern "C" fn wrapper_callback(start: *const c_char, n: c_uint, user_data: *mut c_void) {
let result = unsafe { &mut *(user_data as *mut Option<String>) };
let result = unsafe { &mut *(user_data as *mut Result<String, NixideError>) };
if !start.is_null() && n > 0 {
let bytes = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };
if let Ok(s) = std::str::from_utf8(bytes) {
*result = Some(s.to_string());
}
}
*result = start.to_utf8_string_sized(n as usize);
}
let mut result: Option<String> = None;
let user_data = &mut result as *mut _ as *mut c_void;
let ctx = ErrorContext::new();
let mut user_data: MaybeUninit<Result<String, NixideError>> = MaybeUninit::uninit();
NixErrorCode::from(callback(wrapper_callback, user_data), name)?;
result.ok_or(NixErrorCode::NullPtr { location: name })
callback(
&ctx,
wrapper_callback,
user_data.as_mut_ptr() as *mut c_void,
);
if let Some(err) = ctx.peak() {
return Err(err);
}
unsafe { user_data.assume_init() }
}
pub fn wrap_libnix_pathbuf_callback<F>(
name: &'static str,
callback: F,
) -> Result<PathBuf, NixErrorCode>
pub fn wrap_libnix_pathbuf_callback<F>(callback: F) -> Result<PathBuf, NixideError>
where
F: FnOnce(unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), *mut c_void) -> i32,
F: FnOnce(
&ErrorContext,
unsafe extern "C" fn(*const c_char, c_uint, *mut c_void),
*mut c_void,
) -> i32,
{
wrap_libnix_string_callback(name, callback).map(PathBuf::from)
wrap_libnix_string_callback(callback).map(PathBuf::from)
}

View file

@ -0,0 +1,37 @@
use std::ffi::{c_char, CStr};
use std::slice::from_raw_parts;
use std::str::from_utf8;
use crate::errors::{new_nixide_error, NixideError};
pub trait CCharPtrNixExt {
fn to_utf8_string(self) -> Result<String, NixideError>;
fn to_utf8_string_sized(self, n: usize) -> Result<String, NixideError>;
}
impl CCharPtrNixExt for *const c_char {
fn to_utf8_string(self) -> Result<String, NixideError> {
if self.is_null() {
return Err(new_nixide_error!(NullPtr));
}
let result = unsafe { CStr::from_ptr(self).to_str() };
match result {
Ok(msg_str) => Ok(msg_str.to_string()),
Err(_) => Err(new_nixide_error!(StringNulByte)),
}
}
fn to_utf8_string_sized(self, n: usize) -> Result<String, NixideError> {
if !self.is_null() && n > 0 {
let bytes = unsafe { from_raw_parts(self.cast::<u8>(), n as usize) };
from_utf8(bytes)
.ok()
.map(|s| s.to_string())
.ok_or(new_nixide_error!(NullPtr))
} else {
Err(new_nixide_error!(NullPtr))
}
}
}

View file

@ -1 +1,23 @@
pub mod bindings;
#[macro_use]
pub mod panic;
pub(crate) mod bindings;
mod cchar_nix_ext;
pub mod wrappers;
pub use cchar_nix_ext::CCharPtrNixExt;
pub(crate) use panic::*;
use crate::NixideError;
pub trait AsErr<T> {
fn as_err(self) -> Result<(), T>;
}
impl AsErr<NixideError> for Option<NixideError> {
fn as_err(self) -> Result<(), NixideError> {
match self {
Some(err) => Err(err),
None => Ok(()),
}
}
}

17
nixide/src/util/panic.rs Normal file
View file

@ -0,0 +1,17 @@
macro_rules! panic_issue {
($($arg:expr),*) => {{
panic!(
"{}: please open an issue on https://github.com/cry128/nixide",
format!($($arg),*)
)
}};
}
macro_rules! panic_issue_call_failed {
() => {{
crate::util::panic_issue!("[nixide] call to `{}` failed", stdext::debug_name!())
}};
}
pub(crate) use panic_issue;
pub(crate) use panic_issue_call_failed;

View file

@ -0,0 +1,16 @@
pub trait AsInnerPtr<T> {
/// Get a pointer to the underlying (`inner`) `libnix` C struct.
///
/// # Safety
///
/// Although this function isn't inherently `unsafe`, it is
/// marked as such intentionally to force calls to be wrapped
/// in `unsafe` blocks for clarity.
unsafe fn as_ptr(&self) -> *mut T;
}
pub trait FromC<T> {
/// Creates a new instance of [Self] from the underlying
/// libnix C type [T].
unsafe fn from_c(value: T) -> Self;
}

5
rustfmt.toml Normal file
View file

@ -0,0 +1,5 @@
format_strings = true
group_imports = "Preserve"
imports_granularity = "Module"
reorder_impl_items = true
wrap_comments = true