this is how i stash right? /s
This commit is contained in:
parent
e437571af1
commit
fcc8fede11
31 changed files with 1217 additions and 858 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -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
18
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<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()`
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
300
nixide/src/errors/context.rs
Normal file
300
nixide/src/errors/context.rs
Normal 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
175
nixide/src/errors/error.rs
Normal 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
9
nixide/src/errors/mod.rs
Normal 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;
|
||||
145
nixide/src/errors/nix_error.rs
Normal file
145
nixide/src/errors/nix_error.rs
Normal 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]"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
37
nixide/src/util/cchar_nix_ext.rs
Normal file
37
nixide/src/util/cchar_nix_ext.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
17
nixide/src/util/panic.rs
Normal 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;
|
||||
16
nixide/src/util/wrappers.rs
Normal file
16
nixide/src/util/wrappers.rs
Normal 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
5
rustfmt.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
format_strings = true
|
||||
group_imports = "Preserve"
|
||||
imports_granularity = "Module"
|
||||
reorder_impl_items = true
|
||||
wrap_comments = true
|
||||
Loading…
Add table
Add a link
Reference in a new issue