use std::ffi::c_void;
use super::types::FfiStr;
#[repr(C)]
pub struct FfiGetSecretResult {
pub error_code: i32,
pub error_msg: *mut std::os::raw::c_char,
pub value: *mut std::os::raw::c_char,
}
impl FfiGetSecretResult {
pub fn ok(value: String) -> Self {
match std::ffi::CString::new(value) {
Ok(c_value) => Self {
error_code: 0,
error_msg: std::ptr::null_mut(),
value: c_value.into_raw(),
},
Err(_) => Self::err("secret value contained embedded null byte".to_string()),
}
}
pub fn err(msg: String) -> Self {
let c_msg = std::ffi::CString::new(msg).unwrap_or_else(|_| {
std::ffi::CString::new("(error message contained null bytes)")
.expect("static fallback message has no null bytes")
});
Self {
error_code: 1,
error_msg: c_msg.into_raw(),
value: std::ptr::null_mut(),
}
}
pub unsafe fn into_result(self) -> anyhow::Result<String> {
if self.error_code == 0 {
if self.value.is_null() {
Ok(String::new())
} else {
let s = std::ffi::CStr::from_ptr(self.value)
.to_string_lossy()
.into_owned();
drop(std::ffi::CString::from_raw(self.value));
Ok(s)
}
} else {
let msg = if self.error_msg.is_null() {
"Unknown error".to_string()
} else {
let s = std::ffi::CStr::from_ptr(self.error_msg)
.to_string_lossy()
.into_owned();
drop(std::ffi::CString::from_raw(self.error_msg));
s
};
if !self.value.is_null() {
drop(std::ffi::CString::from_raw(self.value));
}
Err(anyhow::anyhow!(msg))
}
}
}
#[repr(C)]
pub struct SecretStoreProviderVtable {
pub state: *mut c_void,
pub get_secret_fn: extern "C" fn(state: *const c_void, name: FfiStr) -> FfiGetSecretResult,
pub drop_fn: extern "C" fn(state: *mut c_void),
}
unsafe impl Send for SecretStoreProviderVtable {}
unsafe impl Sync for SecretStoreProviderVtable {}
#[cfg(test)]
mod tests {
use super::FfiGetSecretResult;
#[test]
fn ok_returns_error_for_embedded_null_bytes() {
let result = FfiGetSecretResult::ok("abc\0def".to_string());
assert_eq!(result.error_code, 1);
assert!(result.value.is_null());
let err = unsafe { result.into_result() }.unwrap_err();
assert_eq!(err.to_string(), "secret value contained embedded null byte");
}
#[test]
fn err_uses_fallback_message_for_embedded_null_bytes() {
let result = FfiGetSecretResult::err("bad\0message".to_string());
assert_eq!(result.error_code, 1);
assert!(result.value.is_null());
let err = unsafe { result.into_result() }.unwrap_err();
assert_eq!(err.to_string(), "(error message contained null bytes)");
}
}