From fcc8fede1130cb7d9cee58ef82bf8d7bb656f36c Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 21 Mar 2026 10:26:27 +1000 Subject: [PATCH] this is how i stash right? /s --- Cargo.lock | 7 + TODO.md | 18 ++ nixide/Cargo.toml | 1 + nixide/src/context.rs | 233 -------------- nixide/src/error.rs | 183 ----------- nixide/src/errors/context.rs | 300 ++++++++++++++++++ nixide/src/errors/error.rs | 175 ++++++++++ nixide/src/errors/mod.rs | 9 + nixide/src/errors/nix_error.rs | 145 +++++++++ nixide/src/expr/evalstate.rs | 111 +++---- nixide/src/expr/evalstatebuilder.rs | 41 +-- nixide/src/expr/tests.rs | 18 +- nixide/src/expr/value.rs | 196 ++++++------ nixide/src/expr/valuetype.rs | 8 +- nixide/src/flake/eval_state_builder_ext.rs | 6 +- nixide/src/flake/fetchers_settings.rs | 12 +- nixide/src/flake/flake_lock_flags.rs | 98 +++--- nixide/src/flake/flake_reference.rs | 55 ++-- .../src/flake/flake_reference_parse_flags.rs | 55 ++-- nixide/src/flake/flake_settings.rs | 58 ++-- nixide/src/flake/locked_flake.rs | 105 +++--- nixide/src/flake/mod.rs | 2 +- nixide/src/lib.rs | 8 +- nixide/src/store/mod.rs | 6 +- nixide/src/store/path.rs | 73 +++-- nixide/src/util/bindings.rs | 53 ++-- nixide/src/util/cchar_nix_ext.rs | 37 +++ nixide/src/util/mod.rs | 24 +- nixide/src/util/panic.rs | 17 + nixide/src/util/wrappers.rs | 16 + rustfmt.toml | 5 + 31 files changed, 1217 insertions(+), 858 deletions(-) delete mode 100644 nixide/src/context.rs delete mode 100644 nixide/src/error.rs create mode 100644 nixide/src/errors/context.rs create mode 100644 nixide/src/errors/error.rs create mode 100644 nixide/src/errors/mod.rs create mode 100644 nixide/src/errors/nix_error.rs create mode 100644 nixide/src/util/cchar_nix_ext.rs create mode 100644 nixide/src/util/panic.rs create mode 100644 nixide/src/util/wrappers.rs create mode 100644 rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index 5346be7..ff32633 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/TODO.md b/TODO.md index 075a528..e8fdabf 100644 --- a/TODO.md +++ b/TODO.md @@ -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` +- [ ] `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()` diff --git a/nixide/Cargo.toml b/nixide/Cargo.toml index fc0fa79..e97d5fe 100644 --- a/nixide/Cargo.toml +++ b/nixide/Cargo.toml @@ -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" diff --git a/nixide/src/context.rs b/nixide/src/context.rs deleted file mode 100644 index 94ab5f6..0000000 --- a/nixide/src/context.rs +++ /dev/null @@ -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 pos; -// std::list 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 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` -type NixResult = Result; - -#[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 last_err = {}; -/// std::optional 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, -} - -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 { - // 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::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 { - 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 { - 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 { - 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 { - 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()); - } - } -} diff --git a/nixide/src/error.rs b/nixide/src/error.rs deleted file mode 100644 index b870591..0000000 --- a/nixide/src/error.rs +++ /dev/null @@ -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, - }, - - /// 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( - result: Result, - location: &'static str, - ) -> Result { - result.or(Err(NixErrorCode::NulError { location })) - } - - pub fn new_nonnull(ptr: *mut T, location: &'static str) -> Result, 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}") - } -} diff --git a/nixide/src/errors/context.rs b/nixide/src/errors/context.rs new file mode 100644 index 0000000..91c5cec --- /dev/null +++ b/nixide/src/errors/context.rs @@ -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 pos; +// std::list 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 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 last_err = {}; +/// std::optional 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? or is that excessive?) + inner: NonNull, +} + +impl AsInnerPtr 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::from_error_context(self) + } + + /// + /// Equivalent to running `self.peak()` then `self.clear()` + pub fn pop(&mut self) -> Option { + 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 { + 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 { + // 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 { + // 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 { + // 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()); + } + } +} diff --git a/nixide/src/errors/error.rs b/nixide/src/errors/error.rs new file mode 100644 index 0000000..b47694e --- /dev/null +++ b/nixide/src/errors/error.rs @@ -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 = Result; + +#[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 { + 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}`" + ), + } + } +} diff --git a/nixide/src/errors/mod.rs b/nixide/src/errors/mod.rs new file mode 100644 index 0000000..4ab5ca5 --- /dev/null +++ b/nixide/src/errors/mod.rs @@ -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; diff --git a/nixide/src/errors/nix_error.rs b/nixide/src/errors/nix_error.rs new file mode 100644 index 0000000..39e6825 --- /dev/null +++ b/nixide/src/errors/nix_error.rs @@ -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), + + /// 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 { +// #[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]" + ), + } + } +} diff --git a/nixide/src/expr/evalstate.rs b/nixide/src/expr/evalstate.rs index ded2a10..bdb7884 100644 --- a/nixide/src/expr/evalstate.rs +++ b/nixide/src/expr/evalstate.rs @@ -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, + + // XXX: TODO: is an `Arc` necessary or just a `Store` #[allow(dead_code)] store: Arc, - pub(super) context: Arc, +} + +impl AsInnerPtr 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, - store: Arc, - context: Arc, - ) -> Self { - Self { - inner, - store, - context, - } + pub(super) fn new(inner: NonNull, store: Arc) -> 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, 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 { + 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, 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 { + 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)), + }, + } } } diff --git a/nixide/src/expr/evalstatebuilder.rs b/nixide/src/expr/evalstatebuilder.rs index ef417db..25cf117 100644 --- a/nixide/src/expr/evalstatebuilder.rs +++ b/nixide/src/expr/evalstatebuilder.rs @@ -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, store: Arc, - context: Arc, } impl EvalStateBuilder { @@ -25,19 +26,16 @@ impl EvalStateBuilder { /// # Errors /// /// Returns an error if the builder cannot be created. - pub fn new(store: &Arc) -> Result { + pub fn new(store: &Arc) -> Result { // 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 { + pub fn build(self) -> Result { + 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 { diff --git a/nixide/src/expr/tests.rs b/nixide/src/expr/tests.rs index 054fce9..1bcc04a 100644 --- a/nixide/src/expr/tests.rs +++ b/nixide/src/expr/tests.rs @@ -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") diff --git a/nixide/src/expr/value.rs b/nixide/src/expr/value.rs index 32b6220..48a10e4 100644 --- a/nixide/src/expr/value.rs +++ b/nixide/src/expr/value.rs @@ -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, - pub(crate) state: &'a EvalState, } -impl Value<'_> { +impl AsInnerPtr 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 { - 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 { + 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 { - 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 { + 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 { - 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 { + 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 { - 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 { + 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::(), 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 { + pub fn to_nix_string(&self) -> Result { 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 { diff --git a/nixide/src/expr/valuetype.rs b/nixide/src/expr/valuetype.rs index 408bd65..62ed8cd 100644 --- a/nixide/src/expr/valuetype.rs +++ b/nixide/src/expr/valuetype.rs @@ -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 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"), } } } diff --git a/nixide/src/flake/eval_state_builder_ext.rs b/nixide/src/flake/eval_state_builder_ext.rs index 3c3acac..ff8598f 100644 --- a/nixide/src/flake/eval_state_builder_ext.rs +++ b/nixide/src/flake/eval_state_builder_ext.rs @@ -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; + fn flakes(self, settings: &FlakeSettings) -> Result; } impl EvalStateBuilderExt for EvalStateBuilder { /// Configures the eval state to provide flakes features such as `builtins.getFlake`. - fn flakes(mut self, settings: &FlakeSettings) -> Result { + fn flakes(mut self, settings: &FlakeSettings) -> Result { settings.add_to_eval_state_builder(&mut self).map(|_| self) } } diff --git a/nixide/src/flake/fetchers_settings.rs b/nixide/src/flake/fetchers_settings.rs index 8332695..ac4f5ca 100644 --- a/nixide/src/flake/fetchers_settings.rs +++ b/nixide/src/flake/fetchers_settings.rs @@ -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, } impl FetchersSettings { - pub fn new() -> Result { - let ctx = ErrorContext::new()?; + pub fn new() -> Result { + 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))?, }) } diff --git a/nixide/src/flake/flake_lock_flags.rs b/nixide/src/flake/flake_lock_flags.rs index d006fc5..bb066ec 100644 --- a/nixide/src/flake/flake_lock_flags.rs +++ b/nixide/src/flake/flake_lock_flags.rs @@ -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 { - 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 { + 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(()), + } } } diff --git a/nixide/src/flake/flake_reference.rs b/nixide/src/flake/flake_reference.rs index 09c31d8..3979e2a 100644 --- a/nixide/src/flake/flake_reference.rs +++ b/nixide/src/flake/flake_reference.rs @@ -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, + pub(crate) ptr: NonNull, } + 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)), + } } } diff --git a/nixide/src/flake/flake_reference_parse_flags.rs b/nixide/src/flake/flake_reference_parse_flags.rs index 2353d94..39bbf78 100644 --- a/nixide/src/flake/flake_reference_parse_flags.rs +++ b/nixide/src/flake/flake_reference_parse_flags.rs @@ -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, } + 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 { - 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 { + 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() } } diff --git a/nixide/src/flake/flake_settings.rs b/nixide/src/flake/flake_settings.rs index 687b56d..a16d68b 100644 --- a/nixide/src/flake/flake_settings.rs +++ b/nixide/src/flake/flake_settings.rs @@ -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, } +impl AsInnerPtr for FlakeSettings { + unsafe fn as_ptr(&self) -> *mut sys::nix_flake_settings { + self.inner.as_ptr() + } +} + impl FlakeSettings { - pub fn new() -> Result { - 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 { + 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(()), + } } } diff --git a/nixide/src/flake/locked_flake.rs b/nixide/src/flake/locked_flake.rs index 8348319..f11732c 100644 --- a/nixide/src/flake/locked_flake.rs +++ b/nixide/src/flake/locked_flake.rs @@ -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, + pub(crate) ptr: NonNull, } 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 { - 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 { + 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 { - 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 { + 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, diff --git a/nixide/src/flake/mod.rs b/nixide/src/flake/mod.rs index b6a4133..51ae925 100644 --- a/nixide/src/flake/mod.rs +++ b/nixide/src/flake/mod.rs @@ -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; diff --git a/nixide/src/lib.rs b/nixide/src/lib.rs index 3bb26ed..90ffab7 100644 --- a/nixide/src/lib.rs +++ b/nixide/src/lib.rs @@ -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; diff --git a/nixide/src/store/mod.rs b/nixide/src/store/mod.rs index bbfe92e..9aa7f44 100644 --- a/nixide/src/store/mod.rs +++ b/nixide/src/store/mod.rs @@ -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") } } diff --git a/nixide/src/store/path.rs b/nixide/src/store/path.rs index aecc896..c298eb4 100644 --- a/nixide/src/store/path.rs +++ b/nixide/src/store/path.rs @@ -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, - pub(crate) _context: Arc, } 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, - store: &Store, - path: &str, - ) -> Result { - 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 { + 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 { - wrap_libnix_string_callback("nix_store_path_name", |callback, user_data| unsafe { + /// + pub fn name(&self) -> Result { + 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 { - 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 { + 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. diff --git a/nixide/src/util/bindings.rs b/nixide/src/util/bindings.rs index cd8b4f8..50408e6 100644 --- a/nixide/src/util/bindings.rs +++ b/nixide/src/util/bindings.rs @@ -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( - name: &'static str, - callback: F, -) -> Result +pub fn wrap_libnix_string_callback(callback: F) -> Result 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) }; + let result = unsafe { &mut *(user_data as *mut Result) }; - if !start.is_null() && n > 0 { - let bytes = unsafe { std::slice::from_raw_parts(start.cast::(), 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 = None; - let user_data = &mut result as *mut _ as *mut c_void; + let ctx = ErrorContext::new(); + let mut user_data: MaybeUninit> = 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( - name: &'static str, - callback: F, -) -> Result +pub fn wrap_libnix_pathbuf_callback(callback: F) -> Result 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) } diff --git a/nixide/src/util/cchar_nix_ext.rs b/nixide/src/util/cchar_nix_ext.rs new file mode 100644 index 0000000..82b9fc8 --- /dev/null +++ b/nixide/src/util/cchar_nix_ext.rs @@ -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; + + fn to_utf8_string_sized(self, n: usize) -> Result; +} + +impl CCharPtrNixExt for *const c_char { + fn to_utf8_string(self) -> Result { + 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 { + if !self.is_null() && n > 0 { + let bytes = unsafe { from_raw_parts(self.cast::(), n as usize) }; + from_utf8(bytes) + .ok() + .map(|s| s.to_string()) + .ok_or(new_nixide_error!(NullPtr)) + } else { + Err(new_nixide_error!(NullPtr)) + } + } +} diff --git a/nixide/src/util/mod.rs b/nixide/src/util/mod.rs index 90c70dc..5635219 100644 --- a/nixide/src/util/mod.rs +++ b/nixide/src/util/mod.rs @@ -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 { + fn as_err(self) -> Result<(), T>; +} + +impl AsErr for Option { + fn as_err(self) -> Result<(), NixideError> { + match self { + Some(err) => Err(err), + None => Ok(()), + } + } +} diff --git a/nixide/src/util/panic.rs b/nixide/src/util/panic.rs new file mode 100644 index 0000000..7a22501 --- /dev/null +++ b/nixide/src/util/panic.rs @@ -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; diff --git a/nixide/src/util/wrappers.rs b/nixide/src/util/wrappers.rs new file mode 100644 index 0000000..1f6af3b --- /dev/null +++ b/nixide/src/util/wrappers.rs @@ -0,0 +1,16 @@ +pub trait AsInnerPtr { + /// 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 { + /// Creates a new instance of [Self] from the underlying + /// libnix C type [T]. + unsafe fn from_c(value: T) -> Self; +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3a45084 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,5 @@ +format_strings = true +group_imports = "Preserve" +imports_granularity = "Module" +reorder_impl_items = true +wrap_comments = true