neorg_dirman/
c_functions.rs

1use crate::workspace::Workspace;
2use std::{
3    ffi::{c_char, CString},
4    mem::ManuallyDrop,
5};
6
7#[repr(C)]
8pub struct FileList {
9    pub data: *const *const c_char,
10    pub length: usize,
11    pub _capacity: usize,
12}
13
14#[no_mangle]
15pub unsafe extern "C" fn create_workspace(
16    name: *const c_char,
17    path: *const c_char,
18) -> *mut Workspace {
19    assert!(!name.is_null(), "Parameter `name` must not be `null`!");
20    assert!(!path.is_null(), "Parameter `path` must not be `null`!");
21
22    let (name, path) = (
23        std::ffi::CStr::from_ptr(name),
24        std::ffi::CStr::from_ptr(path),
25    );
26    let workspace = Workspace {
27        name: name.to_string_lossy().to_string(),
28        path: path.to_string_lossy().to_string().into(),
29    };
30
31    Box::into_raw(workspace.into())
32}
33
34#[no_mangle]
35pub unsafe extern "C" fn workspace_files(workspace: *const Workspace) -> *mut FileList {
36    assert!(
37        !workspace.is_null(),
38        "Parameter `workspace` must not be `null`!"
39    );
40
41    let files = ManuallyDrop::new(
42        (*workspace)
43            .files()
44            .into_iter()
45            .map(|path| {
46                CString::new(path.to_string_lossy().into_owned())
47                    .unwrap()
48                    .into_raw() as *const c_char
49            })
50            .collect::<Vec<*const c_char>>(),
51    );
52
53    let file_list = FileList {
54        data: files.as_ptr(),
55        length: files.len(),
56        _capacity: files.capacity(),
57    };
58
59    Box::into_raw(file_list.into())
60}
61
62#[no_mangle]
63pub unsafe extern "C" fn destroy_files(file_list: *mut FileList) {
64    if file_list.is_null() {
65        return;
66    }
67
68    let file_list = Box::from_raw(file_list);
69
70    let file_path_ptrs: Vec<*mut c_char> = Vec::from_raw_parts(
71        file_list.data as *mut *mut c_char,
72        file_list.length,
73        file_list._capacity,
74    );
75
76    for file_path_ptr in file_path_ptrs {
77        drop(CString::from_raw(file_path_ptr));
78    }
79}
80
81#[no_mangle]
82pub unsafe extern "C" fn destroy_workspace(workspace: *mut Workspace) {
83    if workspace.is_null() {
84        return;
85    }
86
87    drop(Box::from_raw(workspace));
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_workspace_ffi_boundary() {
96        unsafe {
97            let name = CString::new("test").unwrap().into_raw();
98            let path = CString::new("test/example_workspace/").unwrap().into_raw();
99
100            let workspace = create_workspace(name, path);
101
102            destroy_workspace(workspace);
103
104            drop(Box::from_raw(name));
105            drop(Box::from_raw(path));
106        }
107    }
108
109    #[test]
110    fn test_workspace_files_ffi_boundary() {
111        unsafe {
112            let name = CString::new("test").unwrap().into_raw();
113            let path = CString::new("test/example_workspace/").unwrap().into_raw();
114
115            let workspace = create_workspace(name, path);
116
117            let files = workspace_files(workspace);
118
119            destroy_files(files);
120            destroy_workspace(workspace);
121
122            drop(Box::from_raw(name));
123            drop(Box::from_raw(path));
124        }
125    }
126}