Skip to main content

agentic_codebase/ffi/
c_api.rs

1//! C-compatible FFI bindings.
2//!
3//! Thin wrappers around the public API. Opaque pointers. No Rust types exposed.
4//! All functions use `panic::catch_unwind` for safety and null-pointer checks.
5
6use std::ffi::CStr;
7use std::os::raw::c_char;
8use std::path::Path;
9
10use crate::format::AcbReader;
11use crate::graph::CodeGraph;
12
13// ========================================================================
14// Error codes
15// ========================================================================
16
17/// Success.
18pub const ACB_OK: i32 = 0;
19/// I/O error.
20pub const ACB_ERR_IO: i32 = -1;
21/// Invalid argument.
22pub const ACB_ERR_INVALID: i32 = -2;
23/// Not found.
24pub const ACB_ERR_NOT_FOUND: i32 = -3;
25/// Buffer overflow.
26pub const ACB_ERR_OVERFLOW: i32 = -4;
27/// Null pointer.
28pub const ACB_ERR_NULL_PTR: i32 = -5;
29
30// ========================================================================
31// Graph lifecycle
32// ========================================================================
33
34/// Load a code graph from an `.acb` file. Returns handle or NULL on failure.
35///
36/// # Safety
37///
38/// `path` must be a valid, non-null, null-terminated C string.
39#[no_mangle]
40pub unsafe extern "C" fn acb_graph_open(path: *const c_char) -> *mut std::ffi::c_void {
41    std::panic::catch_unwind(|| {
42        if path.is_null() {
43            return std::ptr::null_mut();
44        }
45        let path_str = unsafe { CStr::from_ptr(path) };
46        let path_str = match path_str.to_str() {
47            Ok(s) => s,
48            Err(_) => return std::ptr::null_mut(),
49        };
50        match AcbReader::read_from_file(Path::new(path_str)) {
51            Ok(graph) => Box::into_raw(Box::new(graph)) as *mut std::ffi::c_void,
52            Err(_) => std::ptr::null_mut(),
53        }
54    })
55    .unwrap_or(std::ptr::null_mut())
56}
57
58/// Free a graph handle.
59///
60/// # Safety
61///
62/// `graph` must be a valid handle returned by `acb_graph_open`, or null.
63/// Must not be called more than once for the same handle.
64#[no_mangle]
65pub unsafe extern "C" fn acb_graph_free(graph: *mut std::ffi::c_void) {
66    if !graph.is_null() {
67        let _ = std::panic::catch_unwind(|| unsafe {
68            drop(Box::from_raw(graph as *mut CodeGraph));
69        });
70    }
71}
72
73// ========================================================================
74// Graph metadata
75// ========================================================================
76
77/// Get the number of code units in the graph.
78///
79/// # Safety
80///
81/// `graph` must be a valid, non-null handle from `acb_graph_open`.
82#[no_mangle]
83pub unsafe extern "C" fn acb_graph_unit_count(graph: *mut std::ffi::c_void) -> u64 {
84    std::panic::catch_unwind(|| {
85        if graph.is_null() {
86            return 0;
87        }
88        let graph = unsafe { &*(graph as *const CodeGraph) };
89        graph.unit_count() as u64
90    })
91    .unwrap_or(0)
92}
93
94/// Get the number of edges in the graph.
95///
96/// # Safety
97///
98/// `graph` must be a valid, non-null handle from `acb_graph_open`.
99#[no_mangle]
100pub unsafe extern "C" fn acb_graph_edge_count(graph: *mut std::ffi::c_void) -> u64 {
101    std::panic::catch_unwind(|| {
102        if graph.is_null() {
103            return 0;
104        }
105        let graph = unsafe { &*(graph as *const CodeGraph) };
106        graph.edge_count() as u64
107    })
108    .unwrap_or(0)
109}
110
111/// Get the embedding dimension.
112///
113/// # Safety
114///
115/// `graph` must be a valid, non-null handle from `acb_graph_open`.
116#[no_mangle]
117pub unsafe extern "C" fn acb_graph_dimension(graph: *mut std::ffi::c_void) -> u32 {
118    std::panic::catch_unwind(|| {
119        if graph.is_null() {
120            return 0;
121        }
122        let graph = unsafe { &*(graph as *const CodeGraph) };
123        graph.dimension() as u32
124    })
125    .unwrap_or(0)
126}
127
128// ========================================================================
129// Unit access
130// ========================================================================
131
132/// Get a unit's name. Writes to buffer. Returns name length or error code.
133///
134/// # Safety
135///
136/// `graph` must be a valid handle. `buffer` must point to at least
137/// `buffer_size` bytes of writable memory.
138#[no_mangle]
139pub unsafe extern "C" fn acb_graph_get_unit_name(
140    graph: *mut std::ffi::c_void,
141    unit_id: u64,
142    buffer: *mut c_char,
143    buffer_size: u32,
144) -> i32 {
145    std::panic::catch_unwind(|| {
146        if graph.is_null() || buffer.is_null() {
147            return ACB_ERR_NULL_PTR;
148        }
149        let graph = unsafe { &*(graph as *const CodeGraph) };
150        match graph.get_unit(unit_id) {
151            Some(unit) => {
152                let name_bytes = unit.name.as_bytes();
153                if name_bytes.len() + 1 > buffer_size as usize {
154                    return ACB_ERR_OVERFLOW;
155                }
156                unsafe {
157                    std::ptr::copy_nonoverlapping(
158                        name_bytes.as_ptr(),
159                        buffer as *mut u8,
160                        name_bytes.len(),
161                    );
162                    *buffer.add(name_bytes.len()) = 0; // null terminator
163                }
164                name_bytes.len() as i32
165            }
166            None => ACB_ERR_NOT_FOUND,
167        }
168    })
169    .unwrap_or(ACB_ERR_INVALID)
170}
171
172/// Get a unit's type as a u8. Returns -1 if not found.
173///
174/// # Safety
175///
176/// `graph` must be a valid, non-null handle from `acb_graph_open`.
177#[no_mangle]
178pub unsafe extern "C" fn acb_graph_get_unit_type(
179    graph: *mut std::ffi::c_void,
180    unit_id: u64,
181) -> i32 {
182    std::panic::catch_unwind(|| {
183        if graph.is_null() {
184            return -1;
185        }
186        let graph = unsafe { &*(graph as *const CodeGraph) };
187        graph
188            .get_unit(unit_id)
189            .map(|u| u.unit_type as i32)
190            .unwrap_or(-1)
191    })
192    .unwrap_or(-1)
193}
194
195/// Get a unit's file path. Writes to buffer. Returns path length or error code.
196///
197/// # Safety
198///
199/// `graph` must be a valid handle. `buffer` must point to at least
200/// `buffer_size` bytes of writable memory.
201#[no_mangle]
202pub unsafe extern "C" fn acb_graph_get_unit_file(
203    graph: *mut std::ffi::c_void,
204    unit_id: u64,
205    buffer: *mut c_char,
206    buffer_size: u32,
207) -> i32 {
208    std::panic::catch_unwind(|| {
209        if graph.is_null() || buffer.is_null() {
210            return ACB_ERR_NULL_PTR;
211        }
212        let graph = unsafe { &*(graph as *const CodeGraph) };
213        match graph.get_unit(unit_id) {
214            Some(unit) => {
215                let path_str = unit.file_path.display().to_string();
216                let path_bytes = path_str.as_bytes();
217                if path_bytes.len() + 1 > buffer_size as usize {
218                    return ACB_ERR_OVERFLOW;
219                }
220                unsafe {
221                    std::ptr::copy_nonoverlapping(
222                        path_bytes.as_ptr(),
223                        buffer as *mut u8,
224                        path_bytes.len(),
225                    );
226                    *buffer.add(path_bytes.len()) = 0;
227                }
228                path_bytes.len() as i32
229            }
230            None => ACB_ERR_NOT_FOUND,
231        }
232    })
233    .unwrap_or(ACB_ERR_INVALID)
234}
235
236/// Get a unit's complexity score. Returns -1.0 if not found.
237///
238/// # Safety
239///
240/// `graph` must be a valid, non-null handle from `acb_graph_open`.
241#[no_mangle]
242pub unsafe extern "C" fn acb_graph_get_unit_complexity(
243    graph: *mut std::ffi::c_void,
244    unit_id: u64,
245) -> f32 {
246    std::panic::catch_unwind(|| {
247        if graph.is_null() {
248            return -1.0;
249        }
250        let graph = unsafe { &*(graph as *const CodeGraph) };
251        graph
252            .get_unit(unit_id)
253            .map(|u| u.complexity as f32)
254            .unwrap_or(-1.0)
255    })
256    .unwrap_or(-1.0)
257}
258
259// ========================================================================
260// Edge access
261// ========================================================================
262
263/// Get outgoing edges from a unit. Returns edge count or error code.
264///
265/// # Safety
266///
267/// `graph` must be a valid handle. `target_ids`, `edge_types`, and `weights`
268/// must each point to at least `max_edges` elements of writable memory.
269#[no_mangle]
270pub unsafe extern "C" fn acb_graph_get_edges(
271    graph: *mut std::ffi::c_void,
272    unit_id: u64,
273    target_ids: *mut u64,
274    edge_types: *mut u8,
275    weights: *mut f32,
276    max_edges: u32,
277) -> i32 {
278    std::panic::catch_unwind(|| {
279        if graph.is_null() || target_ids.is_null() || edge_types.is_null() || weights.is_null() {
280            return ACB_ERR_NULL_PTR;
281        }
282        let graph = unsafe { &*(graph as *const CodeGraph) };
283        let edges = graph.edges_from(unit_id);
284        let count = edges.len().min(max_edges as usize);
285        for (i, edge) in edges.iter().take(count).enumerate() {
286            unsafe {
287                *target_ids.add(i) = edge.target_id;
288                *edge_types.add(i) = edge.edge_type as u8;
289                *weights.add(i) = edge.weight;
290            }
291        }
292        count as i32
293    })
294    .unwrap_or(ACB_ERR_INVALID)
295}
296
297/// Get a unit's language. Returns language as u8, or -1 if not found.
298///
299/// # Safety
300///
301/// `graph` must be a valid, non-null handle from `acb_graph_open`.
302#[no_mangle]
303pub unsafe extern "C" fn acb_graph_get_unit_language(
304    graph: *mut std::ffi::c_void,
305    unit_id: u64,
306) -> i32 {
307    std::panic::catch_unwind(|| {
308        if graph.is_null() {
309            return -1;
310        }
311        let graph = unsafe { &*(graph as *const CodeGraph) };
312        graph
313            .get_unit(unit_id)
314            .map(|u| u.language as i32)
315            .unwrap_or(-1)
316    })
317    .unwrap_or(-1)
318}
319
320/// Get a unit's stability score. Returns -1.0 if not found.
321///
322/// # Safety
323///
324/// `graph` must be a valid, non-null handle from `acb_graph_open`.
325#[no_mangle]
326pub unsafe extern "C" fn acb_graph_get_unit_stability(
327    graph: *mut std::ffi::c_void,
328    unit_id: u64,
329) -> f32 {
330    std::panic::catch_unwind(|| {
331        if graph.is_null() {
332            return -1.0;
333        }
334        let graph = unsafe { &*(graph as *const CodeGraph) };
335        graph
336            .get_unit(unit_id)
337            .map(|u| u.stability_score)
338            .unwrap_or(-1.0)
339    })
340    .unwrap_or(-1.0)
341}