use crate::status::KgliteStatusCode;
use crate::strings::alloc_c_string;
use kglite::api::{load_file, save_graph, DirGraph};
use std::ffi::{c_char, CStr};
use std::sync::Arc;
#[repr(C)]
pub struct KgliteGraph {
_opaque: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
pub(crate) struct GraphState {
pub(crate) inner: Arc<DirGraph>,
}
impl GraphState {
pub(crate) fn into_handle(arc: Arc<DirGraph>) -> *mut KgliteGraph {
let boxed = Box::new(GraphState { inner: arc });
Box::into_raw(boxed).cast::<KgliteGraph>()
}
pub(crate) unsafe fn from_handle_mut<'a>(handle: *mut KgliteGraph) -> &'a mut GraphState {
unsafe { &mut *handle.cast::<GraphState>() }
}
pub(crate) unsafe fn free_handle(handle: *mut KgliteGraph) {
if handle.is_null() {
return;
}
let _ = unsafe { Box::from_raw(handle.cast::<GraphState>()) };
}
}
#[no_mangle]
pub unsafe extern "C" fn kglite_load_file(
path: *const c_char,
out_graph: *mut *mut KgliteGraph,
out_error_msg: *mut *const c_char,
) -> KgliteStatusCode {
if path.is_null() || out_graph.is_null() {
return KgliteStatusCode::NullPointer;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return KgliteStatusCode::InvalidUtf8,
};
match load_file(path_str) {
Ok(arc) => {
unsafe {
*out_graph = GraphState::into_handle(arc);
}
if !out_error_msg.is_null() {
unsafe {
*out_error_msg = std::ptr::null();
}
}
KgliteStatusCode::Ok
}
Err(io_err) => {
unsafe {
*out_graph = std::ptr::null_mut();
}
let (code, message) = classify_io_error(&io_err);
if !out_error_msg.is_null() {
unsafe {
*out_error_msg = alloc_c_string(&message);
}
}
code
}
}
}
fn classify_io_error(err: &std::io::Error) -> (KgliteStatusCode, String) {
let code = match err.kind() {
std::io::ErrorKind::NotFound => KgliteStatusCode::FileNotFound,
std::io::ErrorKind::InvalidData => KgliteStatusCode::FileFormat,
_ => KgliteStatusCode::FileIo,
};
(code, err.to_string())
}
#[no_mangle]
pub unsafe extern "C" fn kglite_save_graph(
graph: *mut KgliteGraph,
path: *const c_char,
out_error_msg: *mut *const c_char,
) -> KgliteStatusCode {
if graph.is_null() || path.is_null() {
return KgliteStatusCode::NullPointer;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return KgliteStatusCode::InvalidUtf8,
};
let state = unsafe { GraphState::from_handle_mut(graph) };
match save_graph(&mut state.inner, path_str) {
Ok(()) => {
if !out_error_msg.is_null() {
unsafe {
*out_error_msg = std::ptr::null();
}
}
KgliteStatusCode::Ok
}
Err(msg) => {
if !out_error_msg.is_null() {
unsafe {
*out_error_msg = alloc_c_string(&msg);
}
}
KgliteStatusCode::FileIo
}
}
}
#[no_mangle]
pub unsafe extern "C" fn kglite_graph_free(graph: *mut KgliteGraph) {
unsafe { GraphState::free_handle(graph) };
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn load_nonexistent_file_returns_file_not_found() {
let path = CString::new("/tmp/__kglite_c_does_not_exist__.kgl").unwrap();
let mut graph: *mut KgliteGraph = std::ptr::null_mut();
let mut err: *const c_char = std::ptr::null();
let rc =
unsafe { kglite_load_file(path.as_ptr(), &mut graph as *mut _, &mut err as *mut _) };
assert_eq!(rc, KgliteStatusCode::FileNotFound);
assert!(graph.is_null());
assert!(!err.is_null());
unsafe { crate::kglite_free_string(err) };
}
#[test]
fn load_null_path_returns_null_pointer() {
let mut graph: *mut KgliteGraph = std::ptr::null_mut();
let mut err: *const c_char = std::ptr::null();
let rc =
unsafe { kglite_load_file(std::ptr::null(), &mut graph as *mut _, &mut err as *mut _) };
assert_eq!(rc, KgliteStatusCode::NullPointer);
}
#[test]
fn graph_free_is_null_safe() {
unsafe { kglite_graph_free(std::ptr::null_mut()) };
}
}