use crate::error::{Error, Result};
use crate::ffi;
use std::ffi::{CStr, CString};
const INFO_VALUE_MAX_LEN: i32 = 1024;
pub struct Info {
handle: i32,
is_null: bool,
}
impl Info {
pub fn new() -> Result<Self> {
let mut handle: i32 = 0;
let ret = unsafe { ffi::ferrompi_info_create(&mut handle) };
Error::check_with_op(ret, "info_create")?;
Ok(Info {
handle,
is_null: false,
})
}
pub fn null() -> Self {
Info {
handle: -1,
is_null: true,
}
}
pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
if self.is_null {
return Err(Error::Internal(
"cannot set key-value on MPI_INFO_NULL".into(),
));
}
let c_key =
CString::new(key).map_err(|_| Error::Internal("info key contains null byte".into()))?;
let c_value = CString::new(value)
.map_err(|_| Error::Internal("info value contains null byte".into()))?;
let ret = unsafe { ffi::ferrompi_info_set(self.handle, c_key.as_ptr(), c_value.as_ptr()) };
Error::check_with_op(ret, "info_set")
}
pub fn get(&self, key: &str) -> Result<Option<String>> {
if self.is_null {
return Err(Error::Internal(
"cannot get key-value from MPI_INFO_NULL".into(),
));
}
let c_key =
CString::new(key).map_err(|_| Error::Internal("info key contains null byte".into()))?;
let mut buf = vec![0u8; INFO_VALUE_MAX_LEN as usize];
let mut valuelen: i32 = INFO_VALUE_MAX_LEN;
let mut flag: i32 = 0;
let ret = unsafe {
ffi::ferrompi_info_get(
self.handle,
c_key.as_ptr(),
buf.as_mut_ptr().cast::<std::ffi::c_char>(),
&mut valuelen,
&mut flag,
)
};
Error::check_with_op(ret, "info_get")?;
if flag == 0 {
return Ok(None);
}
let c_str = unsafe { CStr::from_ptr(buf.as_ptr().cast::<std::ffi::c_char>()) };
let value = c_str
.to_str()
.map_err(|_| Error::Internal("info value is not valid UTF-8".into()))?;
Ok(Some(value.to_string()))
}
pub fn raw_handle(&self) -> i32 {
self.handle
}
}
impl Drop for Info {
fn drop(&mut self) {
if !self.is_null && self.handle >= 0 {
unsafe { ffi::ferrompi_info_free(self.handle) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_info_raw_handle() {
let info = Info::null();
assert_eq!(info.raw_handle(), -1);
}
#[test]
fn null_info_set_returns_error() {
let mut info = Info::null();
let result = info.set("key", "value");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("MPI_INFO_NULL"), "got: {err_msg}");
}
#[test]
fn null_info_get_returns_error() {
let info = Info::null();
let result = info.get("key");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("MPI_INFO_NULL"), "got: {err_msg}");
}
#[test]
fn set_key_with_null_byte_returns_error() {
let mut info = Info {
handle: -1,
is_null: false,
};
let result = info.set("key\0bad", "value");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("null byte"), "got: {err_msg}");
}
#[test]
fn set_value_with_null_byte_returns_error() {
let mut info = Info {
handle: -1,
is_null: false,
};
let result = info.set("key", "value\0bad");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("null byte"), "got: {err_msg}");
}
#[test]
fn get_key_with_null_byte_returns_error() {
let info = Info {
handle: -1,
is_null: false,
};
let result = info.get("key\0bad");
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(err_msg.contains("null byte"), "got: {err_msg}");
}
}