nibb_core/ffi/ffi.rs
1use std::ffi::{CStr, CString};
2use std::os::raw::c_char;
3use slug::slugify;
4use crate::ffi::ffi_utils::{c_str_from_str, load_repo_ffi, str_from_c_str};
5use crate::fs::get_nibb_dir;
6use crate::result::NibbFFIError;
7use crate::{FSRepo, Snippet, SnippetRepository};
8use crate::git::git_integration::nibb_git_generic;
9
10/// Loads a snippet by name and returns its JSON representation.
11///
12/// # Arguments
13/// - `name`: A null-terminated C string representing the name of the snippet.
14///
15/// # Returns
16/// A newly allocated C string (`*mut c_char`) containing the snippet's JSON representation.
17/// - On success: JSON-encoded `Snippet` as a C string (must be freed with `free_string_ffi`).
18/// - On failure: JSON-encoded error object (must also be freed).
19///
20/// # Safety
21/// - `name` must be a valid, null-terminated UTF-8 string.
22/// - Caller is responsible for freeing the returned string using `free_string_ffi`.
23#[unsafe(no_mangle)]
24pub extern "C" fn load_snippet_ffi(name: *const c_char) -> *mut c_char {
25 let repo = match load_repo_ffi() {
26 Ok(repo) => repo,
27 Err(e) => return e,
28 };
29
30 let name = unsafe { CStr::from_ptr(name) };
31 let name = match name.to_str() {
32 Ok(name) => name,
33 Err(e) => return c_str_from_str(&NibbFFIError::FFIError(e.to_string()).to_json())
34 };
35 let slug = slugify(name);
36 let snippet = match repo.load(&slug) {
37 Ok(snippet) => snippet,
38 Err(e) => return c_str_from_str(&e.to_json())
39 };
40 c_str_from_str(&snippet.to_json())
41}
42
43/// Saves a single snippet from its JSON representation.
44///
45/// # Arguments
46/// - `snippet_json`: A null-terminated C string containing a JSON-encoded snippet.
47///
48/// # Returns
49/// - `true` if the snippet was saved successfully.
50/// - `false` if the input was invalid or saving failed.
51///
52/// # Safety
53/// - `snippet_json` must be a valid, null-terminated UTF-8 string.
54#[unsafe(no_mangle)]
55pub extern "C" fn save_snippet_ffi(snippet_json: *const c_char) -> bool {
56 let snippet_json = str_from_c_str(snippet_json);
57 let snippet = match serde_json::from_str(&snippet_json) {
58 Ok(snippet) => {
59 snippet
60 }
61 Err(_) => {
62 return false;
63 }
64 };
65 let repo = match FSRepo::new(get_nibb_dir().unwrap()) {
66 Ok(repo) => repo,
67 Err(_) => return false
68 };
69 match repo.save(&snippet) {
70 Ok(_) => true,
71 Err(_) => false,
72 }
73}
74
75/// Deletes a snippet from the repo
76///
77/// # Arguments
78/// - `name`: A Null terminated C String of the snippets' name.
79///
80/// # Returns
81/// - `true` if the snippet was deleted successfully.
82/// - `false` if an error occurred.
83///
84/// # Safety
85/// - `name` needs to be a valid, null-terminated, UTF-8 string.
86#[unsafe(no_mangle)]
87pub extern "C" fn delete_snippet_ffi(name: *const c_char) -> bool {
88 let repo = match FSRepo::new(get_nibb_dir().unwrap()) {
89 Ok(repo) => repo,
90 Err(_) => return false
91 };
92 let name = str_from_c_str(name);
93 match repo.delete(&name) {
94 Ok(_) => true,
95 Err(_) => false,
96 }
97}
98
99/// Loads all snippets from the repository and returns them as a JSON array.
100///
101/// # Returns
102/// A newly allocated C string (`*mut c_char`) containing the JSON array of all snippets.
103/// - On success: JSON array of snippets (must be freed with `free_string_ffi`).
104/// - On failure: JSON-encoded error object (must also be freed).
105///
106/// # Safety
107/// - Caller is responsible for freeing the returned string using `free_string_ffi`.
108#[unsafe(no_mangle)]
109pub extern "C" fn load_all_ffi() -> *mut c_char {
110 let repo = match load_repo_ffi() {
111 Ok(repo) => repo,
112 Err(e) => return e,
113 };
114 let snippets = match repo.load_all() {
115 Ok(snippets) => snippets,
116 Err(e) => return c_str_from_str(&e.to_json())
117 };
118 c_str_from_str(
119 &serde_json::to_string(&snippets)
120 .unwrap_or_else(|_| "{\"type\":\"Other\",\"message\":\"Serialization failed\"}"
121 .to_string())
122 )
123}
124
125/// Saves a list of snippets from a JSON array.
126///
127/// # Arguments
128/// - `snippets_json`: A null-terminated C string containing a JSON array of snippets.
129///
130/// # Returns
131/// - `true` if all snippets were saved successfully.
132/// - `false` if deserialization or saving failed.
133///
134/// # Safety
135/// - `snippets_json` must be a valid, null-terminated UTF-8 string.
136#[unsafe(no_mangle)]
137pub extern "C" fn save_all_ffi(snippets_json: *const c_char) -> bool {
138 let snippets_json = str_from_c_str(snippets_json);
139 let snippets: Vec<Snippet> = match serde_json::from_str(&snippets_json) {
140 Ok(snippets) => {
141 snippets
142 }
143 Err(_) => {
144 return false;
145 }
146 };
147 let repo = match load_repo_ffi() {
148 Ok(repo) => repo,
149 Err(_) => return false,
150 };
151 match repo.save_all(&snippets) {
152 Ok(_) => true,
153 Err(_) => false,
154 }
155
156}
157
158/// Executes a generic Git command inside the `.nibb` directory and returns the output as JSON.
159///
160/// # Arguments
161/// - `args`: A pointer to a null-terminated C string containing the Git arguments as a whitespace-separated string, e.g. `"status -s"`.
162///
163/// # Returns
164/// - A pointer to a null-terminated C string containing a JSON object:
165/// ```json
166/// {
167/// "stdout": "<Git stdout output>",
168/// "stderr": "<Git stderr output>"
169/// }
170/// ```
171/// - In case of error, `stderr` contains an error message and `stdout` is empty.
172///
173/// # Safety
174/// - The input C string must be valid and null-terminated.
175/// - The returned string must be freed by the caller using the appropriate function (e.g., `free_string_ffi`).
176///
177/// # Example (from Lua)
178/// ```lua
179/// local output_json = ffi.C.nibb_git_generic_ffi("status -s")
180/// local output = vim.fn.json_decode(ffi.string(output_json))
181/// print(output.stdout)
182/// print(output.stderr)
183/// ```
184#[unsafe(no_mangle)]
185pub extern "C" fn nibb_git_generic_ffi(args: *const c_char) -> *const c_char{
186 let args = str_from_c_str(args);
187 let args: Vec<String> = args.split_whitespace().map(|s| s.to_string()).collect();
188 let out = nibb_git_generic(args).unwrap_or_else(|e| e.to_json());
189 c_str_from_str(&out)
190}
191
192/// Frees a string previously allocated and returned by an FFI function.
193///
194/// # Arguments
195/// - `s`: A pointer returned by an FFI function like `load_snippet_ffi` or `load_all_ffi`.
196///
197/// # Safety
198/// - `s` must be a pointer obtained from one of the FFI functions using `CString::into_raw`.
199/// - Passing a null pointer is safe and does nothing.
200/// - After calling this function, `s` must not be used again.
201#[unsafe(no_mangle)]
202pub extern "C" fn free_string_ffi(s: *mut c_char) {
203 unsafe {
204 if s.is_null() {
205 return;
206 }
207 drop(CString::from_raw(s));
208 }
209}