use crate::embedded::c_api::config::LinkrsConfigHandle;
use crate::embedded::c_api::error::{
error_code_from_core_error, linkrs_error_code_t, set_last_error_message,
};
use crate::embedded::c_api::types::{linkrs_config_t, linkrs_t};
use crate::embedded::GraphDatabase;
use linkrs_storage::GraphStorage;
use std::ffi::{c_char, c_int, c_void, CStr, CString};
use std::ptr;
use std::sync::Arc;
pub struct LinkrsHandle {
pub(crate) inner: Arc<GraphDatabase<GraphStorage>>,
pub(crate) last_error: Option<CString>,
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_open(path: *const c_char, db: *mut *mut linkrs_t) -> c_int {
if path.is_null() || db.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let path_str = unsafe {
match CStr::from_ptr(path).to_str() {
Ok(s) => s,
Err(_) => return linkrs_error_code_t::LINKRS_MISUSE as c_int,
}
};
match GraphDatabase::open(path_str) {
Ok(linkrs) => {
let handle = Box::new(LinkrsHandle {
inner: Arc::new(linkrs),
last_error: None,
});
unsafe {
*db = Box::into_raw(handle) as *mut linkrs_t;
}
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) => {
let (error_code, _) = error_code_from_core_error(&e);
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
unsafe {
*db = ptr::null_mut();
}
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_open_with_config(
config: *mut linkrs_config_t,
db: *mut *mut linkrs_t,
) -> c_int {
if config.is_null() || db.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
let config_handle = &*(config as *mut LinkrsConfigHandle);
match GraphDatabase::open_with_config(config_handle.inner.clone()) {
Ok(linkrs) => {
let handle = Box::new(LinkrsHandle {
inner: Arc::new(linkrs),
last_error: None,
});
unsafe {
*db = Box::into_raw(handle) as *mut linkrs_t;
}
linkrs_error_code_t::LINKRS_OK as c_int
}
Err(e) => {
let (error_code, _) = error_code_from_core_error(&e);
let error_msg = format!("{}", e);
set_last_error_message(error_msg);
unsafe {
*db = ptr::null_mut();
}
error_code
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_close(db: *mut linkrs_t) -> c_int {
if db.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
unsafe {
let _ = Box::from_raw(db as *mut LinkrsHandle);
}
linkrs_error_code_t::LINKRS_OK as c_int
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_errcode(db: *mut linkrs_t) -> c_int {
if db.is_null() {
return linkrs_error_code_t::LINKRS_MISUSE as c_int;
}
unsafe {
let handle = &*(db as *mut LinkrsHandle);
if handle.last_error.is_some() {
linkrs_error_code_t::LINKRS_ERROR as c_int
} else {
linkrs_error_code_t::LINKRS_OK as c_int
}
}
}
#[no_mangle]
pub extern "C" fn linkrs_libversion() -> *const c_char {
static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
VERSION.as_ptr() as *const c_char
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_free_string(str: *mut c_char) {
if !str.is_null() {
unsafe {
let _ = CString::from_raw(str);
}
}
}
#[no_mangle]
pub unsafe extern "C" fn linkrs_free(ptr: *mut c_void) {
if !ptr.is_null() {
unsafe {
let _ = Box::from_raw(ptr as *mut u8);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU64, Ordering};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn get_test_db_path() -> std::path::PathBuf {
let counter = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let temp_dir = std::env::temp_dir().join("linkrs_c_api_test");
std::fs::create_dir_all(&temp_dir).ok();
let db_path = temp_dir.join(format!("test_db_{}_{}.db", std::process::id(), counter));
if db_path.exists() {
std::fs::remove_file(&db_path).ok();
std::thread::sleep(std::time::Duration::from_millis(50));
}
db_path
}
#[test]
fn test_linkrs_libversion() {
let version = unsafe {
CStr::from_ptr(linkrs_libversion())
.to_str()
.expect("Failed to convert version to str")
};
assert!(!version.is_empty());
}
#[test]
fn test_linkrs_open_close_file() {
let db_path = get_test_db_path();
let path_cstring = CString::new(db_path.to_str().expect("Invalid path"))
.expect("Failed to create CString");
let mut db: *mut linkrs_t = ptr::null_mut();
let rc = unsafe { linkrs_open(path_cstring.as_ptr(), &mut db) };
if rc != linkrs_error_code_t::LINKRS_OK as c_int {
panic!(
"Failed to open database, error code: {}, path: {:?}",
rc, db_path
);
}
assert!(!db.is_null());
let rc = unsafe { linkrs_close(db) };
assert_eq!(rc, linkrs_error_code_t::LINKRS_OK as c_int);
}
#[test]
fn test_linkrs_null_params() {
let rc = unsafe { linkrs_open(ptr::null(), ptr::null_mut()) };
assert_eq!(rc, linkrs_error_code_t::LINKRS_MISUSE as c_int);
let rc = unsafe { linkrs_close(ptr::null_mut()) };
assert_eq!(rc, linkrs_error_code_t::LINKRS_MISUSE as c_int);
}
}