Skip to main content

gap/
cffi.rs

1//! C-compatible FFI for the GAP apply engine.
2//!
3//! Exposes `gap_resolve_envelope` for use via CGo (or any C caller).
4//! Returns heap-allocated JSON strings; caller must free with `gap_free_string`.
5
6use std::ffi::{CStr, CString};
7use std::os::raw::c_char;
8
9use crate::apply;
10use crate::gap::{Artifact, Envelope};
11
12/// Resolve a GAP envelope against an optional base artifact.
13///
14/// # Arguments
15/// * `op_json` - Null-terminated JSON string of the operation envelope.
16/// * `art_json` - Null-terminated JSON string of the base artifact, or NULL for synthesize.
17///
18/// # Returns
19/// Heap-allocated null-terminated JSON string: `{"artifact": {...}, "handle": {...}}`.
20/// Returns NULL on error. Caller must free the result with `gap_free_string`.
21///
22/// # Safety
23/// Both pointers must be valid null-terminated C strings or NULL (for `art_json`).
24#[no_mangle]
25pub unsafe extern "C" fn gap_resolve_envelope(
26    op_json: *const c_char,
27    art_json: *const c_char,
28) -> *mut c_char {
29    let op_str = match unsafe { CStr::from_ptr(op_json) }.to_str() {
30        Ok(s) => s,
31        Err(_) => return std::ptr::null_mut(),
32    };
33
34    let art_str = if art_json.is_null() {
35        None
36    } else {
37        match unsafe { CStr::from_ptr(art_json) }.to_str() {
38            Ok(s) => Some(s),
39            Err(_) => return std::ptr::null_mut(),
40        }
41    };
42
43    match resolve(op_str, art_str) {
44        Ok(s) => CString::new(s)
45            .map(|c| c.into_raw())
46            .unwrap_or(std::ptr::null_mut()),
47        Err(_) => std::ptr::null_mut(),
48    }
49}
50
51/// Free a string previously returned by `gap_resolve_envelope`.
52///
53/// # Safety
54/// The pointer must have been returned by `gap_resolve_envelope` and not yet freed.
55#[no_mangle]
56pub unsafe extern "C" fn gap_free_string(s: *mut c_char) {
57    if !s.is_null() {
58        drop(unsafe { CString::from_raw(s) });
59    }
60}
61
62fn resolve(op_json: &str, art_json: Option<&str>) -> anyhow::Result<String> {
63    let envelope: Envelope = serde_json::from_str(op_json)?;
64    let artifact = art_json
65        .map(serde_json::from_str::<Artifact>)
66        .transpose()?;
67    let (result_artifact, handle) = apply::apply(artifact.as_ref(), &envelope)?;
68    Ok(serde_json::to_string(&serde_json::json!({
69        "artifact": result_artifact,
70        "handle": handle,
71    }))?)
72}