Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/bindings/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ use bindings::{
nixl_capi_query_resp_list_get_params, nixl_capi_prep_xfer_dlist, nixl_capi_release_xfer_dlist_handle,
nixl_capi_make_xfer_req, nixl_capi_get_local_partial_md,
nixl_capi_send_local_partial_md, nixl_capi_query_xfer_backend, nixl_capi_opt_args_set_ip_addr,
nixl_capi_opt_args_set_port, nixl_capi_get_xfer_telemetry
nixl_capi_opt_args_set_port, nixl_capi_get_xfer_telemetry,
nixl_capi_create_params, nixl_capi_params_add
};

// Re-export status codes
Expand Down
84 changes: 82 additions & 2 deletions src/bindings/rust/src/utils/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ impl<'a> Iterator for ParamIterator<'a> {
};

match status {
0 if !has_next => None,
0 if key_ptr.is_null() => None,
0 => {
// SAFETY: If status is 0, both pointers are valid null-terminated strings
// SAFETY: If status is 0 and key_ptr is not null, both pointers are valid null-terminated strings
let result = unsafe {
let key = CStr::from_ptr(key_ptr).to_str().unwrap();
let value = CStr::from_ptr(value_ptr).to_str().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm refering to line 61 (Ok(ParamPair { key, value })) it could be nicer to just return Ok((key, value)) which will enable iterating for key, value:

    for (key, value) in params.iter() {
       ...
    }

It will also allow us to remove the ParamPair object, and use HashMap::from(params.iter())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want the entire ParamPair object removed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want that?

Expand Down Expand Up @@ -82,6 +82,86 @@ impl Params {
Self { inner }
}

/// Creates a new empty Params object
pub(crate) fn create() -> Result<Self, NixlError> {
let mut params = ptr::null_mut();

let status = unsafe { nixl_capi_create_params(&mut params) };

match status {
0 => {
let inner = unsafe { NonNull::new_unchecked(params) };
Ok(Self { inner })
}
-1 => Err(NixlError::InvalidParam),
_ => Err(NixlError::BackendError),
}
}

/// Creates a new Params object from an iteratable
///
/// # Example
/// ```ignore
/// use std::collections::HashMap;
///
/// let map = HashMap::from([
/// ("access_key", "*********"),
/// ("secret_key", "*********"),
/// ("bucket", "my-bucket"),
/// ]);
///
/// let params = Params::try_from_iter(map.iter().map(|(k, v)| (*k, *v)))?;
/// ```
pub fn try_from_iter<I, K, V>(iter: I) -> Result<Self, NixlError>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to call this function from just like in HashMap, following language convention

Copy link
Contributor Author

@cheese-head cheese-head Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it can produce an error, would it be better to implement TryFrom?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most APIs can produce an error, that's what the Result<> return type is for; Result object forces the user code to handle errors.

where
I: IntoIterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
let mut params = Self::create()?;
for (key, value) in iter {
params.set(key.as_ref(), value.as_ref())?;
}
Ok(params)
}

/// Creates a new Params object by copying from another Params
///
/// # Example
/// ```ignore
/// let original_params = agent.get_plugin_params("OBJ")?.1;
/// let mut modified_params = original_params.try_clone()?;
/// modified_params.set("bucket", "my-custom-bucket")?;
/// ```
pub fn try_clone(&self) -> Result<Self, NixlError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can name it clone?

let mut params = Self::create()?;

if let Ok(iter) = self.iter() {
for pair in iter {
let pair = pair?;
params.set(pair.key, pair.value)?;
}
}

Ok(params)
}

/// Sets a key-value pair in the parameters (overwrites if exists)
pub fn set(&mut self, key: &str, value: &str) -> Result<(), NixlError> {
let c_key = CString::new(key)?;
let c_value = CString::new(value)?;

let status = unsafe {
nixl_capi_params_add(self.inner.as_ptr(), c_key.as_ptr(), c_value.as_ptr())
};

match status {
0 => Ok(()),
-1 => Err(NixlError::InvalidParam),
_ => Err(NixlError::BackendError),
}
}

/// Returns true if the parameters are empty
pub fn is_empty(&self) -> Result<bool, NixlError> {
let mut is_empty = false;
Expand Down
10 changes: 10 additions & 0 deletions src/bindings/rust/stubs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@ nixl_capi_opt_args_set_port(nixl_capi_opt_args_t args, uint16_t port) {
return nixl_capi_stub_abort();
}

nixl_capi_status_t
nixl_capi_create_params(nixl_capi_params_t *params) {
return nixl_capi_stub_abort();
}

nixl_capi_status_t
nixl_capi_params_add(nixl_capi_params_t params, const char *key, const char *value) {
return nixl_capi_stub_abort();
}

nixl_capi_status_t
nixl_capi_params_is_empty(nixl_capi_params_t params, bool* is_empty)
{
Expand Down
58 changes: 58 additions & 0 deletions src/bindings/rust/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,64 @@ fn test_params_iteration() {
}
}

#[test]
fn test_params_try_from_iter() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn test_params_try_from_iter() {
fn test_params_from_iter() {

use std::collections::HashMap;

let map = HashMap::from([
("key1", "value1"),
("key2", "value2"),
("key3", "value3"),
]);

let params = Params::try_from_iter(map.iter().map(|(k, v)| (*k, *v)))
.expect("Failed to create params from iterator");
Comment on lines +233 to +234
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let params = Params::try_from_iter(map.iter().map(|(k, v)| (*k, *v)))
.expect("Failed to create params from iterator");
let params = Params::from(&map)
.expect("Failed to create params from iterator");

should work as well


assert!(!params.is_empty().unwrap(), "Params should not be empty");

let mut found_keys = HashMap::new();
for param in params.iter().unwrap() {
let param = param.unwrap();
found_keys.insert(param.key.to_string(), param.value.to_string());
}

assert_eq!(found_keys.len(), 3, "Should have 3 key-value pairs");
assert_eq!(found_keys.get("key1"), Some(&"value1".to_string()));
assert_eq!(found_keys.get("key2"), Some(&"value2".to_string()));
assert_eq!(found_keys.get("key3"), Some(&"value3".to_string()));
}

#[test]
fn test_params_try_clone() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn test_params_try_clone() {
fn test_params_clone() {

let agent = Agent::new("test_agent").expect("Failed to create agent");
let (_mems, original_params) = agent
.get_plugin_params("UCX")
.expect("Failed to get plugin params");

let copied_params = original_params.try_clone()
.expect("Failed to copy params");

assert_eq!(
original_params.is_empty().unwrap(),
copied_params.is_empty().unwrap(),
"Copied params should have same empty state"
);

let mut original_map = std::collections::HashMap::new();
for param in original_params.iter().unwrap() {
let param = param.unwrap();
original_map.insert(param.key.to_string(), param.value.to_string());
}

let mut copied_map = std::collections::HashMap::new();
for param in copied_params.iter().unwrap() {
let param = param.unwrap();
copied_map.insert(param.key.to_string(), param.value.to_string());
}
Comment on lines +273 to +276
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can do HashMap::from(copied_params) if Params iterator provides a key and a value?


assert_eq!(original_map, copied_map, "Copied params should match original");
}

// #[test]
// fn test_get_backend_params() {
// let agent = Agent::new("test_agent").unwrap();
Expand Down
31 changes: 31 additions & 0 deletions src/bindings/rust/wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,37 @@ nixl_capi_opt_args_set_port(nixl_capi_opt_args_t args, uint16_t port) {
return NIXL_CAPI_SUCCESS;
}

nixl_capi_status_t
nixl_capi_create_params(nixl_capi_params_t *params) {
if (!params) {
return NIXL_CAPI_ERROR_INVALID_PARAM;
}

try {
auto param_list = new nixl_capi_params_s;
*params = param_list;
return NIXL_CAPI_SUCCESS;
}
catch (...) {
return NIXL_CAPI_ERROR_BACKEND;
}
}

nixl_capi_status_t
nixl_capi_params_add(nixl_capi_params_t params, const char *key, const char *value) {
if (!params || !key || !value) {
return NIXL_CAPI_ERROR_INVALID_PARAM;
}

try {
params->params[key] = value;
return NIXL_CAPI_SUCCESS;
}
catch (...) {
return NIXL_CAPI_ERROR_BACKEND;
}
}

nixl_capi_status_t
nixl_capi_params_is_empty(nixl_capi_params_t params, bool* is_empty)
{
Expand Down
4 changes: 4 additions & 0 deletions src/bindings/rust/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ nixl_capi_status_t
nixl_capi_opt_args_set_port(nixl_capi_opt_args_t args, uint16_t port);

// Parameter access functions
nixl_capi_status_t
nixl_capi_create_params(nixl_capi_params_t *params);
nixl_capi_status_t
nixl_capi_params_add(nixl_capi_params_t params, const char *key, const char *value);
nixl_capi_status_t nixl_capi_params_is_empty(nixl_capi_params_t params, bool* is_empty);
nixl_capi_status_t nixl_capi_params_create_iterator(nixl_capi_params_t params, nixl_capi_param_iter_t* iter);
nixl_capi_status_t nixl_capi_params_iterator_next(
Expand Down